Compare commits

...

68 Commits

Author SHA1 Message Date
DYefremov
43bf5ac44b bump version 2023-04-25 20:53:42 +03:00
DYefremov
e714b10431 changed columns for filtering 2023-04-25 20:52:44 +03:00
DYefremov
f0d0813e75 refactoring of getting picon pixbuf 2023-04-25 01:04:38 +03:00
DYefremov
9dc4df73c4 added piconSNPblack logo 2023-04-25 00:32:44 +03:00
DYefremov
f2da1e4cd4 minor refactoring of player 2023-04-25 00:12:29 +03:00
DYefremov
3d4588833b vlc module update 2023-04-24 22:49:26 +03:00
DYefremov
4cf19e5413 minor code adjustment
* Preventing mixins [str -> Enum] bug in Python 3.11 [100458]
2023-04-22 18:33:18 +03:00
audi06_19
cd4a814838 Turkish translation update (#176) 2023-04-22 17:31:42 +03:00
DYefremov
d7c49f50f2 bump version 2023-04-19 14:07:53 +03:00
DYefremov
b61b8e16fa small fix for Web import options init 2023-04-19 09:56:17 +03:00
DYefremov
56d2a3e991 fix dir copy for FTP tab 2023-04-17 23:16:00 +03:00
DYefremov
b65ea9c0d3 vlc module update 2023-04-17 13:53:46 +03:00
DYefremov
a62ee8f378 extension API improvement 2023-04-15 16:49:20 +03:00
DYefremov
0db8ee6d47 prevent focus lack for main views 2023-04-14 10:48:37 +03:00
DYefremov
ed41b01f63 it *.mo file update 2023-04-13 10:27:24 +03:00
mapi68
c2eaecb8b8 Italian translation update (#174) 2023-04-13 10:20:26 +03:00
DYefremov
f5313f2c40 minor translations update 2023-04-12 23:40:50 +03:00
DYefremov
c6a0b80fdd bouquets gen improvement 2023-04-12 23:30:07 +03:00
DYefremov
7813aeb059 reverted lowercase for some prefixes (#170) 2023-04-12 09:34:50 +03:00
DYefremov
3a0e5c09a1 it *.mo file update 2023-04-09 10:42:48 +03:00
mapi68
ab6a44dc3f Italian translation update (#173) 2023-04-09 10:38:27 +03:00
audi06_19
caefb4587d Update: Turkish translation update (#172) 2023-04-09 10:37:42 +03:00
DYefremov
93ff78d7ce version update 2023-04-08 14:32:00 +03:00
DYefremov
1d583ecd99 fix root bouquet rename 2023-04-08 14:24:12 +03:00
DYefremov
d4914ac451 Russian, Belarusian and German translations update 2023-04-08 00:21:16 +03:00
DYefremov
6207b6a10d Spanish and Dutch translations update 2023-04-07 20:51:13 +03:00
DYefremov
1eb8fe621d save satellites selection for web import 2023-04-07 19:01:16 +03:00
DYefremov
79b41b1661 minor refactoring of settings save 2023-04-07 18:37:40 +03:00
DYefremov
7a36ba8148 minor fix 2023-04-07 13:22:39 +03:00
DYefremov
08e970fc96 save source for web import 2023-04-07 08:42:04 +03:00
DYefremov
9681fcbc79 saving settings for web import 2023-04-07 00:27:55 +03:00
DYefremov
7b002b208f added additional yt link checking (#170) 2023-04-06 14:55:30 +03:00
DYefremov
95a1732f01 satellites merge support for web import (#165) 2023-04-06 00:24:45 +03:00
DYefremov
57e4fdff7f changed case for yt links prefixes (#170) 2023-04-04 22:37:06 +03:00
DYefremov
89c456993f allowed to rename root bouquet 2023-04-03 09:12:18 +03:00
DYefremov
2f3fc31023 minor fix 2023-04-02 12:49:00 +03:00
DYefremov
5c18e49cf7 added 'No' prefix for yt links (#170) 2023-04-02 12:42:59 +03:00
DYefremov
64530bcb85 prefix support for yt playlist import (#170) 2023-03-30 18:57:01 +03:00
DYefremov
4d472609b4 url prefix elems for yt dialog 2023-03-30 00:13:25 +03:00
DYefremov
640b995ab8 prefix support for yt links (#170) 2023-03-27 00:01:08 +03:00
DYefremov
42980a988f minor ui changes for IPTV dialogs 2023-03-26 18:18:32 +03:00
DYefremov
64c5f28957 enabled quality change for yt links 2023-03-26 16:22:12 +03:00
DYefremov
a32bf230cf fix yt link double check on edit (#170) 2023-03-25 16:04:11 +03:00
audi06_19
b8cac728a8 Turkish translation update (#166) 2023-03-12 16:28:55 +03:00
DYefremov
079f07cfd2 mpv module update 2023-03-09 08:47:57 +03:00
DYefremov
9a5884cc9a it *.mo file update 2023-03-06 16:49:55 +03:00
mapi68
115237a10f Italian translation update (#163) 2023-03-06 16:46:29 +03:00
DYefremov
994bd0ee1c Russian, Belarusian and German translations update 2023-03-05 14:01:02 +03:00
DYefremov
27e5b373a3 version update 2023-03-04 20:43:15 +03:00
DYefremov
43c05b1739 fix namespace for web import 2023-03-04 20:00:13 +03:00
DYefremov
bd96c286e9 option skip c-band for web import dialog 2023-03-04 18:15:16 +03:00
DYefremov
1bded41eab close options button for import dialog 2023-03-04 16:00:41 +03:00
DYefremov
5f0f51679c sat list init on update dialog start 2023-03-04 12:28:10 +03:00
DYefremov
380bb3150b updated it *.mo file 2023-03-04 11:15:23 +03:00
mapi68
d1a7a486a2 Italian translation update (#162) 2023-03-04 11:07:52 +03:00
DYefremov
1be167bec3 sorting by pos on bouquets generation 2023-03-04 00:27:37 +03:00
DYefremov
dd3e88589c Russian, Belarusian and German translations update 2023-03-03 17:38:29 +03:00
DYefremov
c5a2df6d7d prevent duplicate for web import 2023-03-03 12:04:04 +03:00
DYefremov
c9fc3803c7 fix FEC value for web import 2023-03-03 10:49:06 +03:00
DYefremov
6afd518cfc fix adding duplicates to the main list 2023-03-03 10:36:46 +03:00
DYefremov
02a51c9b56 bouquets generation for kos web source 2023-03-02 17:50:31 +03:00
DYefremov
c96cfa0e1b category and lang for kingofsat 2023-03-01 23:02:11 +03:00
DYefremov
08bc4ff4c4 bouquets only option for import dialog 2023-03-01 13:36:20 +03:00
DYefremov
ae2b78e990 README update 2023-02-28 00:23:00 +03:00
DYefremov
88be9fe49c updated it *.mo file 2023-02-27 23:48:01 +03:00
mapi68
9dae9b7219 Italian translation update (#161) 2023-02-27 23:42:58 +03:00
DYefremov
f296a6c90b paths normalization in settings 2023-02-27 23:40:41 +03:00
DYefremov
0486776d83 minor fix 2023-02-27 10:31:20 +03:00
50 changed files with 5202 additions and 3743 deletions

View File

@@ -110,7 +110,7 @@ just load your data via *"File/Open"* and press *"Save"*. When importing separat
**The built-in Telnet client does not support ANSI escape sequences!**
For streams playback, this app supports [VLC](https://www.videolan.org/vlc/), [MPV](https://mpv.io/) and [GStreamer](https://gstreamer.freedesktop.org/). Depending on your distro, you may need to install additional packages and libraries.z
For streams playback, this app supports [VLC](https://www.videolan.org/vlc/), [MPV](https://mpv.io/) and [GStreamer](https://gstreamer.freedesktop.org/). Depending on your distro, you may need to install additional packages and libraries.
#### Command line arguments:
* **-l** - write logs to file.
* **-d on/off** - turn on/off debug mode. Allows to display more information in the logs.

View File

@@ -131,33 +131,37 @@ class UtfFTP(FTP):
def download_dir(self, path, save_path, callback=None):
""" Downloads directory from FTP with all contents.
Creates a leaf directory and all intermediate ones. This is recursive.
Creates a leaf directory and all intermediate ones. This is recursive.
"""
os.makedirs(os.path.join(save_path, path), exist_ok=True)
dir_path = os.path.join(save_path, path, "")
os.makedirs(dir_path, exist_ok=True)
current_path = self.pwd()
files = []
self.dir(path, files.append)
try:
self.cwd(path)
except all_errors as e:
msg = f"Download dir error: {e}".rstrip()
log(msg)
return f"500 {msg}"
for f in files:
f_data = self.get_file_data(f)
f_path = f_data[8]
if f_data[0][0] == "d":
try:
os.makedirs(os.path.join(save_path, f_path), exist_ok=True)
except OSError as e:
msg = "Download dir error: {}".format(e).rstrip()
log(msg)
return "500 " + msg
else:
self.download_dir(f_path, save_path, callback)
self.download_dir(f_path, dir_path, callback)
else:
try:
self.download_file(f_path, save_path, callback)
self.download_file(f_path, dir_path, callback)
except OSError as e:
log("Download dir error: {}".format(e).rstrip())
log(f"Download dir error: {e}".rstrip())
self.cwd(current_path)
resp = "226 Transfer complete."
msg = "Copy directory {}. Status: {}".format(path, resp)
msg = f"Copying directory: {path}. Status: {resp}"
log(msg)
if callback:
@@ -648,6 +652,9 @@ class HttpAPI:
N_ZAP = "zapto"
N_STREAM = "build_playlist?id="
def __str__(self):
return self.value
class Remote(str, Enum):
""" Args for HttpRequestType [REMOTE] class. """
ONE = "2"
@@ -682,6 +689,9 @@ class HttpAPI:
NEXT = "407"
BACK = "412"
def __str__(self):
return self.value
class Power(str, Enum):
""" Args for HttpRequestType [POWER] class. """
TOGGLE_STANDBY = "0"
@@ -691,6 +701,9 @@ class HttpAPI:
WAKEUP = "4"
STANDBY = "5"
def __str__(self):
return self.value
PARAM_REQUESTS = {Request.REMOTE,
Request.POWER,
Request.VOL,

View File

@@ -30,6 +30,7 @@
import re
from app.commons import log
from app.eparser.satxml import get_pos_str
from app.ui.uicommons import CODED_ICON, LOCKED_ICON, HIDE_ICON
from .blacklist import get_blacklist
from ..ecommons import Service, POLARIZATION, FEC, SERVICE_TYPE, Flag, T_FEC, TrType, FEC_DEFAULT, T_SYSTEM
@@ -220,8 +221,7 @@ class LameDbReader:
freq = f"{int(freq) // 1000}"
rate = f"{int(rate) // 1000}"
if tr_type is TrType.Satellite:
pos = int(pos)
pos = f"{abs(pos / 10):0.1f}{'W' if pos < 0 else 'E'}"
pos = get_pos_str(int(pos))
except ValueError as e:
log(f"Parse error [parse_services]: {e}")

View File

@@ -153,12 +153,13 @@ def export_to_m3u(path, bouquet, s_type, url=None):
file.writelines(lines)
def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_type=1):
""" Returns fav id depending on the profile. """
def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_type=1, force_quote=True):
""" Returns fav id depending on the settings type. """
if settings_type is SettingsType.ENIGMA_2:
st_type = st_type or StreamType.NONE_TS.value
params = params or (0, 0, 0, 0)
return ENIGMA2_FAV_ID_FORMAT.format(st_type, s_id, srv_type, *params, quote(url), name, name, None)
url = quote(url) if force_quote else url
return ENIGMA2_FAV_ID_FORMAT.format(st_type, s_id, srv_type, *params, url, name, name, None)
elif settings_type is SettingsType.NEUTRINO_MP:
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -192,5 +192,10 @@ def indent(elem, parent=None, index=-1, level=0, space=" "):
elem.tail = f"\n{space * (level - 1)}"
def get_pos_str(pos: int) -> str:
""" Converts satellite position int value to readable string. """
return f"{abs(pos / 10):0.1f}{'W' if pos < 0 else 'E'}"
if __name__ == "__main__":
pass

View File

@@ -429,7 +429,7 @@ class Settings:
@default_data_path.setter
def default_data_path(self, value):
self._settings["default_data_path"] = value
self._settings["default_data_path"] = Settings.normalize_path(value)
@property
def default_backup_path(self):
@@ -437,7 +437,7 @@ class Settings:
@default_backup_path.setter
def default_backup_path(self, value):
self._settings["default_backup_path"] = value
self._settings["default_backup_path"] = Settings.normalize_path(value)
@property
def default_picon_path(self):
@@ -445,7 +445,7 @@ class Settings:
@default_picon_path.setter
def default_picon_path(self, value):
self._settings["default_picon_path"] = value
self._settings["default_picon_path"] = Settings.normalize_path(value)
@property
def profile_data_path(self):
@@ -481,7 +481,7 @@ class Settings:
@recordings_path.setter
def recordings_path(self, value):
self._settings["recordings_path"] = value
self._settings["recordings_path"] = Settings.normalize_path(value)
# ******** Streaming ********* #
@@ -903,13 +903,14 @@ class Settings:
# **************** Get-Set settings **************** #
@staticmethod
def get_settings():
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
Settings.write_settings(Settings.get_default_settings())
def get_settings(config_file=CONFIG_FILE, default_settings=None):
if not os.path.isfile(config_file) or os.stat(config_file).st_size == 0:
df = Settings.get_default_settings() if default_settings is None else default_settings
Settings.write_settings(df, config_file=config_file)
with open(CONFIG_FILE, "r", encoding="utf-8") as config_file:
with open(config_file, "r", encoding="utf-8") as cf:
try:
return json.load(config_file)
return json.load(cf)
except ValueError as e:
raise SettingsReadException(e)
@@ -941,10 +942,14 @@ class Settings:
"ab": "192", "channels": "2", "samplerate": "44100", "scodec": "none"}}
@staticmethod
def write_settings(config):
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
with open(CONFIG_FILE, "w", encoding="utf-8") as config_file:
json.dump(config, config_file, indent=" ")
def write_settings(config, config_path=CONFIG_PATH, config_file=CONFIG_FILE):
os.makedirs(os.path.dirname(config_path), exist_ok=True)
with open(config_file, "w", encoding="utf-8") as cf:
json.dump(config, cf, indent=" ")
@staticmethod
def normalize_path(path):
return f"{os.path.normpath(path)}{SEP}"
if __name__ == "__main__":

View File

@@ -41,6 +41,8 @@ class Player(Gtk.DrawingArea):
def __init__(self, mode, widget, **kwargs):
super().__init__(**kwargs)
self._mode = mode
self._is_playing = False
GObject.signal_new("error", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
@@ -198,9 +200,6 @@ class MpvPlayer(Player):
log(f"{__class__.__name__}: Load library error: {e}")
raise ImportError("No libmpv is found. Check that it is installed!")
else:
self._mode = mode
self._is_playing = False
@self._player.event_callback(mpv.MpvEventID.FILE_LOADED)
def on_open(event):
log("Starting playback...")
@@ -241,8 +240,9 @@ class MpvPlayer(Player):
self._is_playing = True
def stop(self):
self._player.stop()
self._is_playing = True
if self._is_playing:
self._player.stop()
self._is_playing = False
def pause(self):
self._player.pause = not self._player.pause
@@ -252,8 +252,9 @@ class MpvPlayer(Player):
@run_task
def release(self):
self._player.terminate()
self.__INSTANCE = None
if self._player:
self._player.terminate()
self.__INSTANCE = None
def is_playing(self):
return self._is_playing
@@ -290,8 +291,6 @@ class GstPlayer(Player):
self.STATE = Gst.State
self.STAT_RETURN = Gst.StateChangeReturn
self._mode = mode
self._is_playing = False
self._player = Gst.ElementFactory.make("playbin", "player")
self._player.set_window_handle(self.get_window_handle())
@@ -329,8 +328,9 @@ class GstPlayer(Player):
self._is_playing = True
def stop(self):
log("Stop playback...")
self._player.set_state(self.STATE.READY)
if self._is_playing:
log("Stop playback...")
self._player.set_state(self.STATE.READY)
self._is_playing = False
def pause(self):
@@ -345,9 +345,10 @@ class GstPlayer(Player):
@run_task
def release(self):
self._is_playing = False
self._player.set_state(self.STATE.NULL)
self.__INSTANCE = None
if self._player:
self._is_playing = False
self._player.set_state(self.STATE.NULL)
self.__INSTANCE = None
def set_mrl(self, mrl):
self._player.set_property("uri", mrl)
@@ -420,9 +421,6 @@ class VlcPlayer(Player):
log(f"{__class__.__name__}: Load library error: {e}")
raise ImportError("No VLC is found. Check that it is installed!")
else:
self._mode = mode
self._is_playing = False
ev_mgr = self._player.event_manager()
ev_mgr.event_attach(EventType.MediaPlayerVout, self.on_playback_start)
ev_mgr.event_attach(EventType.MediaPlayerTimeChanged,
@@ -449,7 +447,7 @@ class VlcPlayer(Player):
def stop(self):
if self._is_playing:
self._player.stop()
self._is_playing = False
self._is_playing = False
def pause(self):
self._player.pause()

View File

@@ -29,12 +29,13 @@ from functools import partial, wraps
from warnings import warn
if os.name == 'nt':
dll = ctypes.util.find_library('mpv-1.dll')
dll = ctypes.util.find_library('libmpv-2.dll') or ctypes.util.find_library('mpv-1.dll')
if dll is None:
raise OSError('Cannot find mpv-1.dll in your system %PATH%. One way to deal with this is to ship mpv-1.dll '
'with your script and put the directory your script is in into %PATH% before "import mpv": '
'os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"] '
'If mpv-1.dll is located elsewhere, you can add that path to os.environ["PATH"].')
raise OSError(
'Cannot find [lib]mpv-*.dll in your system %PATH%. One way to deal with this is to ship [lib]mpv-*.dll '
'with your script and put the directory your script is in into %PATH% before "import mpv": '
'os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"] '
'If mpv-1.dll is located elsewhere, you can add that path to os.environ["PATH"].')
backend = CDLL(dll)
fs_enc = 'utf-8'
else:

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -221,7 +221,8 @@ class PiconsCzDownloader:
"piconblack80": "b50",
"piconblack3d": "b50",
"piconwin11": "win11220",
"piconSNPtransparent": "t50"
"piconSNPtransparent": "t50",
"piconSNPblack": "b50",
}
def get_name_map(self):

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -429,7 +429,7 @@ class SatellitesParser(HTMLParser):
class ServicesParser(HTMLParser):
""" Services parser for LYNGSAT source. """
def __init__(self, source=SatelliteSource.LYNGSAT, entities=False, separator=' '):
def __init__(self, source=SatelliteSource.LYNGSAT, entities=False, separator=' ', lang=None):
HTMLParser.__init__(self)
@@ -452,6 +452,12 @@ class ServicesParser(HTMLParser):
self._KING_TR_PAT = re.compile((r"(DVB-S[2]?)\s?(?:T2-MI,\s+PLP\s+(\d+))?.*"
r"?(?:PLS:\s+(Root|Gold|Combo)\+(\d+))?"
r"\s+(.*PSK).*?(?:.*Stream\s+(\d+))?.*"))
self._lang = "en"
if lang:
langs = {"en", "fr", "nl", "de", "se", "no", "pt", "es", "it", "pl",
"cz", "gr", "fi", "ar", "tr", "ru", "sc", "ro", "hu", "sq"}
lang, _, _ = lang.partition("_")
self._lang = lang if lang in langs else self._lang
self._parse_html_entities = entities
self._separator = separator
@@ -580,7 +586,7 @@ class ServicesParser(HTMLParser):
if len(r) == 13 and SatellitesParser.POS_PAT.match(r[0].text):
t_cell = r[4]
if t_cell.url and t_cell.url.startswith("tp.php?tp="):
t_cell.url = f"https://en.kingofsat.net/{t_cell.url}"
t_cell.url = f"https://{self._lang}.kingofsat.net/{t_cell.url}"
t_cell.text = f"{r[2].text} {r[3].text} {r[6].text} {r[8].text}"
trs.append(t_cell)
return trs
@@ -730,11 +736,12 @@ class ServicesParser(HTMLParser):
s_type = self._S_TYPES.get(s_type, "3")
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3"))
reg, grp = r[3].text, r[4].text
name, pkg, cas, sid, v_pid, a_pid = r[2].text, r[5].text, r[6].text, r[7].text, None, None
flags, sid, fav_id, picon_id, data_id = self.get_service_data(s_type, pkg, sid, tid, nid, nsp,
v_pid, a_pid, cas, use_pids)
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, None, picon_id,
services.append(Service(flags, "s", None, name, reg, grp, pkg, _s_type, None, picon_id,
sid, str(freq), sr, pol, fec, sys, pos, data_id, fav_id, multi_tr or tr))
return services
@@ -743,9 +750,9 @@ class ServicesParser(HTMLParser):
""" Returns converted transponder data. """
sys = get_key_by_value(SYSTEM, sys)
mod = get_key_by_value(MODULATION, mod)
fec = get_key_by_value(FEC, fec)
fec = get_key_by_value(FEC, fec) or "0"
# For negative (West) positions: 3600 - numeric position value!!!
namespace = f"{3600 - pos if pos < 0 else pos:04x}0000"
namespace = f"{3600 - abs(pos) if pos < 0 else pos:04x}0000"
tr_flag = 1
roll_off = 0 # 35% DVB-S2/DVB-S (default)
pilot = 2 # Auto

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -129,8 +129,7 @@ class YouTube:
fmts = streaming_data.get("formats", None) if streaming_data else None
if fmts:
links = {Quality[i["itag"]]: i["url"] for i in filter(
lambda i: i.get("itag", -1) in Quality, fmts) if "url" in i}
links = {Quality[i["itag"]]: i["url"] for i in fmts if i.get("itag", -1) in Quality and "url" in i}
if links and title:
return links, title.replace("+", " ")

View File

@@ -27,7 +27,7 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor. -->
@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">3.4.2 Beta</property>
<property name="version">3.6.2 Beta</property>
<property name="copyright">2018-2023 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>

View File

@@ -55,6 +55,113 @@ Author: Dmitriy Yefremov
<property name="icon-name">document-revert-symbolic-rtl</property>
<property name="icon_size">1</property>
</object>
<object class="GtkPopover" id="options_popover">
<property name="can-focus">False</property>
<child>
<object class="GtkBox" id="options_popover_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="replace_existing_settings_box">
<property name="visible">True</property>
<property name="sensitive" bind-source="bouquets_only_switch" bind-property="active" bind-flags="invert-boolean">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables overwriting existing main list services.</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="replace_existing_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Replace existing</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="replace_existing_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="bouquets_settings_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables skipping services import from lamedb.</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="bouquets_only_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Bouquets data only</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="bouquets_only_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">center</property>
<signal name="state-set" handler="on_bouquets_only_switch" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkModelButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="text" translatable="yes">Close</property>
<property name="centered">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
@@ -233,30 +340,6 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="replace_existing_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Replace existing</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="replace_existing_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="details_button">
<property name="visible">True</property>
@@ -275,6 +358,53 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="options_menu_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="direction">none</property>
<property name="popover">options_popover</property>
<child>
<object class="GtkBox" id="options_nutton_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage" id="options_button_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Options</property>
<property name="icon-name">applications-system-symbolic</property>
<property name="icon_size">1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="options_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Options</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@@ -121,6 +121,7 @@ class ImportDialog:
"on_resize": self.on_resize,
"on_main_paned_realize": self.on_main_paned_realize,
"on_visible_page": self.on_visible_page,
"on_bouquets_only_switch": self.on_bouquets_only_switch,
"on_key_press": self.on_key_press}
builder = get_builder(f"{UI_RESOURCES_PATH}imports.glade", handlers)
@@ -144,7 +145,10 @@ class ImportDialog:
self._dialog_window.set_transient_for(app.app_window)
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("message_label")
# Options.
self._replace_existing_switch = builder.get_object("replace_existing_switch")
self._bouquets_only_switch = builder.get_object("bouquets_only_switch")
self._bouquets_settings_box = builder.get_object("bouquets_settings_box")
# Bouquets page.
self._bq_model = builder.get_object("bq_list_store")
self._bq_view = builder.get_object("bq_view")
@@ -260,7 +264,12 @@ class ImportDialog:
with suppress(ValueError):
bq.remove(b)
self._append(self._bouquets, list(filter(lambda s: s.fav_id not in self._ids, services)))
if self._bouquets_only_switch.get_active():
services = ()
else:
services = list(filter(lambda s: s.fav_id not in self._ids, services))
self._append(self._bouquets, services)
if self._replace_existing_switch.get_active():
self._app.emit("services_update", {s.fav_id: s for s in filter(lambda s: s.fav_id in self._ids, services)})
@@ -408,6 +417,11 @@ class ImportDialog:
def on_visible_page(self, stack, param):
self._page = Page(stack.get_visible_child_name())
self._bouquets_settings_box.set_sensitive(self._page is Page.SERVICES)
def on_bouquets_only_switch(self, switch, state):
if state:
self._replace_existing_switch.set_active(False)
def on_key_press(self, view, event):
""" Handling keystrokes """

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,7 @@ _DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
_URL_PREFIXES = {"YT-DLP": "YT-DLP://", "YT-DL": "YT-DL://", "STREAMLINK": "streamlink://", "No": None}
def is_data_correct(elems):
@@ -81,6 +82,7 @@ class IptvDialog:
handlers = {"on_response": self.on_response,
"on_entry_changed": self.on_entry_changed,
"on_url_changed": self.on_url_changed,
"on_url_paste": self.on_url_paste,
"on_save": self.on_save,
"on_stream_type_changed": self.on_stream_type_changed,
"on_yt_quality_changed": self.on_yt_quality_changed,
@@ -93,6 +95,7 @@ class IptvDialog:
self._bouquet = bouquet
self._yt_links = None
self._yt_dl = None
self._inserted_url = False
builder = get_builder(_UI_PATH, handlers, use_str=True,
objects=("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
@@ -116,8 +119,10 @@ class IptvDialog:
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._yt_quality_box = builder.get_object("yt_iptv_quality_combobox")
self._url_prefix_box = builder.get_object("iptv_url_prefix_box")
self._url_prefix_combobox = builder.get_object("iptv_url_prefix_combobox")
self._model, self._paths = view.get_selection().get_selected_rows()
# style
# Style.
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._digit_elems = (self._srv_id_entry, self._srv_type_entry, self._sid_entry, self._tr_id_entry,
@@ -134,6 +139,8 @@ class IptvDialog:
else:
self._description_entry.set_visible(False)
builder.get_object("iptv_description_label").set_visible(False)
[self._url_prefix_combobox.append(v, k) for k, v in _URL_PREFIXES.items()]
self._url_prefix_combobox.set_active(0)
if self._action is Action.ADD:
self._save_button.set_visible(False)
@@ -160,6 +167,13 @@ class IptvDialog:
self.show_info_message(get_message("Error. Verify the data!"), Gtk.MessageType.ERROR)
return
url = self._url_entry.get_text()
if all((self._url_prefix_box.get_visible(),
self._url_prefix_combobox.get_active_id(),
url.count("http") > 1 or urlparse(url).scheme.upper() in _URL_PREFIXES)):
self.show_info_message(get_message("Invalid prefix for the given URL!"), Gtk.MessageType.ERROR)
return
if show_dialog(DialogType.QUESTION, self._dialog) in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
@@ -194,7 +208,7 @@ class IptvDialog:
elif stream_type is StreamType.E_SERVICE_HLS:
self._stream_type_combobox.set_active(5)
except ValueError:
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
self.show_info_message(f"Unknown stream type {s_type}", Gtk.MessageType.ERROR)
self._srv_id_entry.set_text(data[1])
self._srv_type_entry.set_text(str(int(data[2], 16)))
@@ -202,7 +216,17 @@ class IptvDialog:
self._tr_id_entry.set_text(str(int(data[4], 16)))
self._net_id_entry.set_text(str(int(data[5], 16)))
self._namespace_entry.set_text(str(int(data[6], 16)))
self._url_entry.set_text(unquote(data[10].strip()))
# URL.
url = unquote(data[10].strip())
sch = urlparse(url).scheme.upper()
if YouTube.get_yt_id(url) and sch in _URL_PREFIXES:
active_prefix = _URL_PREFIXES.get(sch)
url = re.sub(active_prefix, "", url, 1, re.IGNORECASE)
self._url_prefix_combobox.set_active_id(active_prefix)
else:
self._url_prefix_combobox.set_active(len(_URL_PREFIXES) - 1)
self._url_entry.set_text(url)
self.update_reference_entry()
def init_neutrino_data(self, fav_id):
@@ -212,7 +236,6 @@ class IptvDialog:
def update_reference_entry(self):
if self._s_type is SettingsType.ENIGMA_2 and is_data_correct(self._digit_elems):
self.on_url_changed(self._url_entry)
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
self._srv_id_entry.get_text(),
int(self._srv_type_entry.get_text()),
@@ -242,15 +265,25 @@ class IptvDialog:
if yt_id:
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
text = "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
if show_dialog(DialogType.QUESTION, self._dialog, text=text) == Gtk.ResponseType.OK:
entry.set_sensitive(False)
gen = self.set_yt_url(entry, yt_id)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
if self._inserted_url and url_str.count("http") == 1:
if show_dialog(DialogType.QUESTION, self._dialog, text=text) == Gtk.ResponseType.OK:
entry.set_sensitive(False)
gen = self.set_yt_url(entry, yt_id)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
else:
self._url_prefix_box.set_visible(self._s_type is SettingsType.ENIGMA_2)
else:
self._url_prefix_box.set_visible(self._s_type is SettingsType.ENIGMA_2)
self._inserted_url = False
elif YouTube.is_yt_video_link(url_str):
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
else:
entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
self._yt_quality_box.set_visible(False)
self._url_prefix_box.set_visible(False)
def on_url_paste(self, entry):
self._inserted_url = True
self._yt_quality_box.set_visible(False)
def set_yt_url(self, entry, video_id):
try:
@@ -264,7 +297,7 @@ class IptvDialog:
links, title = self._yt_dl.get_yt_link(video_id, entry.get_text())
yield True
except urllib.error.URLError as e:
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
self.show_info_message(f"{get_message('Getting link error:')} {e}", Gtk.MessageType.ERROR)
return
except YouTubeException as e:
self.show_info_message((str(e)), Gtk.MessageType.ERROR)
@@ -279,7 +312,7 @@ class IptvDialog:
entry.set_text(links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]])
self._yt_links = links
else:
msg = get_message("Getting link error:") + " No link received for id: {}".format(video_id)
msg = f"{get_message('Getting link error:')} No link received for id: {video_id}"
self.show_info_message(msg, Gtk.MessageType.ERROR)
finally:
entry.set_sensitive(True)
@@ -291,13 +324,25 @@ class IptvDialog:
self.update_reference_entry()
def on_yt_quality_changed(self, box):
if not self._yt_links:
return
model = box.get_model()
active = model.get_value(box.get_active_iter(), 0)
if self._yt_links and active in self._yt_links:
if active in self._yt_links:
self._url_entry.set_text(self._yt_links[active])
else:
self._url_entry.set_text(self._yt_links.get(max(self._yt_links, default=None), ""))
def save_enigma2_data(self):
name = self._name_entry.get_text().strip()
if self._url_prefix_box.get_visible():
prefix = self._url_prefix_combobox.get_active_id()
url = self._url_entry.get_text().replace(':', '%3A', 1 if prefix else -1)
url = f"{quote(prefix) if prefix else ''}{url}"
else:
url = quote(self._url_entry.get_text())
fav_id = ENIGMA2_FAV_ID_FORMAT.format(self.get_type(),
self._srv_id_entry.get_text(),
int(self._srv_type_entry.get_text()),
@@ -305,8 +350,7 @@ class IptvDialog:
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
int(self._namespace_entry.get_text()),
quote(self._url_entry.get_text()),
name, name)
url, name, name)
self.update_bouquet_data(name, fav_id)
@@ -887,9 +931,14 @@ class YtListImportDialog:
self._import_button = builder.get_object("yt_import_button")
self._quality_box = builder.get_object("yt_quality_combobox")
self._quality_model = builder.get_object("yt_quality_liststore")
self._import_button.bind_property("visible", self._quality_box, "visible")
self._import_button.bind_property("sensitive", self._quality_box, "sensitive")
self._receive_button.bind_property("sensitive", self._import_button, "sensitive")
self._extract_switch = builder.get_object("yt_extract_links_switch")
self._url_prefix_combobox = builder.get_object("yt_url_prefix_combobox")
[self._url_prefix_combobox.append(v, k) for k, v in _URL_PREFIXES.items()]
self._url_prefix_combobox.set_active(0)
builder.get_object("yt_extract_links_box").set_visible(self._s_type is SettingsType.ENIGMA_2)
builder.get_object("yt_url_prefix_box").set_visible(self._s_type is SettingsType.ENIGMA_2)
if self._settings.use_header_bar:
header_bar = HeaderBar(title="YouTube", subtitle=get_message("Playlist import"))
@@ -905,19 +954,31 @@ class YtListImportDialog:
window_size = self._settings.get("yt_import_dialog_size")
if window_size:
self._dialog.resize(*window_size)
# Style
# Style.
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
style_provider.load_from_path(f"{UI_RESOURCES_PATH}style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
def show(self):
self._dialog.show()
@run_task
def on_import(self, item):
self.on_info_bar_close()
self.update_active_elements(False)
if self._extract_switch.get_active():
self.extract_direct_links()
else:
prefix = self._url_prefix_combobox.get_active_id()
selected = filter(lambda r: r[2], self._model)
prefix = quote(prefix) if prefix else ''
links = [(f"{prefix}https{quote(':')}//www.youtube.com/watch?v={r[1]}", r[0]) for r in selected]
self.append_services(links)
self.update_active_elements(True)
@run_task
def extract_direct_links(self):
self._download_task = True
try:
@@ -946,7 +1007,6 @@ class YtListImportDialog:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
if self._download_task:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
self.append_services([done_links[r] for r in rows])
finally:
self._download_task = False
@@ -989,22 +1049,31 @@ class YtListImportDialog:
aggr = [None] * 9
srvs = []
if self._yt_list_title:
if self._yt_list_title and self._s_type is SettingsType.ENIGMA_2:
title = self._yt_list_title
fav_id = MARKER_FORMAT.format(0, title, title)
mk = Service(None, None, None, title, *aggr[0:3], BqServiceType.MARKER.name, *aggr, 0, fav_id, None)
srvs.append(mk)
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
extract = self._extract_switch.get_active()
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0) if extract else None
for link in links:
lnk, title = link or (None, None)
if not lnk:
continue
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
fav_id = get_fav_id(ln, title, self._s_type)
if extract:
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
else:
ln = lnk
fav_id = get_fav_id(ln, title, self._s_type, force_quote=extract)
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
srvs.append(srv)
self.appender(srvs)
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
@run_idle
def update_active_elements(self, sensitive):

View File

@@ -1657,7 +1657,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.4.2 Beta</property>
<property name="label">3.6.2 Beta</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>

View File

@@ -687,6 +687,19 @@ class Application(Gtk.Application):
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 = {}
switchable = []
default = []
def ac(a, v):
c = extensions[a.get_name()]
e = c(self)
e.exec()
def sw(a, v):
c = extensions[a.get_name()]
a.set_state(v)
e = c(self)
e.exec() if v else e.stop()
for importer, name, is_package in pkgutil.iter_modules(ext_paths):
if is_package:
@@ -694,17 +707,25 @@ class Application(Gtk.Application):
cls_name = name.capitalize()
if hasattr(m, cls_name):
cls = getattr(m, cls_name)
if cls.EMBEDDED:
cls(self)
continue
action_name = f"on_{name}_extension"
item = Gio.MenuItem.new(cls.LABEL, f"app.{action_name}")
ext_section.append_item(item)
extensions[action_name] = cls
def ac(a, v):
c = extensions[a.get_name()]
e = c(self)
e.exec()
if cls.SWITCHABLE:
switchable.append(item)
self.set_state_action(action_name, sw, False)
else:
default.append(item)
self.set_action(action_name, ac)
self.set_action(action_name, ac)
switchable.sort(key=lambda i: i.get_attribute_value("label"), reverse=True)
default.sort(key=lambda i: i.get_attribute_value("label"), reverse=True)
[ext_section.append_item(item) for item in switchable]
[ext_section.append_item(item) for item in default]
def init_actions(self):
# Main actions.
@@ -1364,6 +1385,10 @@ class Application(Gtk.Application):
selection = view.get_selection()
model, paths = selection.get_selected_rows()
if not paths:
self.show_error_message("No selected item!")
return
model_name = get_base_model(model).get_name()
itrs = [model.get_iter(path) for path in paths]
rows = [model[in_itr][:] for in_itr in itrs]
@@ -2414,7 +2439,10 @@ class Application(Gtk.Application):
break
def append_services(self, services):
to_add = []
for srv in services:
if srv.fav_id not in self._services:
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()))
@@ -2422,7 +2450,7 @@ class Application(Gtk.Application):
self._services_load_spinner.start()
factor = self.DEL_FACTOR / 4
for index, srv in enumerate(services):
for index, srv in enumerate(to_add):
background = self.get_new_background(srv.flags_cas)
s = srv + (None, background)
self._services_model.append(s)
@@ -2901,6 +2929,11 @@ class Application(Gtk.Application):
self.update_bouquet_list()
def on_view_focus(self, view, focus_event=None):
# Preventing focus lack for some cases.
if not focus_event and not view.is_focus():
view.grab_focus()
return True
model_name, model = get_model_data(view)
not_empty = len(model) > 0 if model else False
is_service = model_name == self.SERVICE_MODEL
@@ -3245,26 +3278,26 @@ class Application(Gtk.Application):
if self._s_type is not SettingsType.ENIGMA_2:
self.show_error_message("Not allowed in this context!")
return
ServicesUpdateDialog(self._main_window, self._settings, self.on_import_data_from_web).show()
ServicesUpdateDialog(self).show()
@run_idle
def on_import_data_from_web(self, services):
def on_import_data_from_web(self, services, bouquets=None):
msg = "Combine with the current data?"
def clb():
self.show_info_message("Done!")
if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window,
msg) == Gtk.ResponseType.OK:
gen = self.append_imported_data([], services)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
gen = self.append_imported_data(bouquets or [], services, clb)
else:
gen = self.import_data_from_web(services)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
gen = self.import_data_from_web(services, bouquets, clb)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def import_data_from_web(self, services):
def import_data_from_web(self, services, bouquets, callback=None):
self._wait_dialog.show()
if self._app_info_box.get_visible():
yield from self.create_new_configuration(self._s_type)
yield from self.append_services(services)
self.update_sat_positions()
yield True
yield from self.create_new_configuration(self._s_type)
yield from self.append_imported_data(bouquets or [], services, callback)
self._wait_dialog.hide()
# ***************** Export ******************** #
@@ -3822,7 +3855,7 @@ class Application(Gtk.Application):
r[Column.SRV_PACKAGE],
r[Column.SRV_TYPE],
r[Column.SRV_SSID],
r[Column.SRV_POS])).upper()))
r[Column.SRV_FREQ])).upper()))
def update_iptv_filter_cache(self):
self._iptv_filter_cache.clear()
@@ -3887,7 +3920,7 @@ class Application(Gtk.Application):
return self.show_error_message("Data loading in progress!")
model, paths = view.get_selection().get_selected_rows()
if is_only_one_item_selected(paths, self._main_window):
if is_only_one_item_selected(paths, self):
model_name = get_base_model(model).get_name()
if model_name == self.FAV_MODEL:
srv_type = model.get_value(model.get_iter(paths), Column.FAV_TYPE)
@@ -3912,7 +3945,7 @@ class Application(Gtk.Application):
def on_bouquets_edit(self, view):
""" Renaming bouquets. """
if not self._bq_selected:
if not self._bq_selected and self._s_type is SettingsType.NEUTRINO_MP:
self.show_error_message("This item is not allowed to edit!")
return
@@ -3920,7 +3953,7 @@ class Application(Gtk.Application):
if paths:
itr = model.get_iter(paths[0])
bq_name, bq_type = model.get(itr, 0, 3)
bq_name, bq_type = model.get(itr, Column.BQ_NAME, Column.BQ_TYPE)
response = show_dialog(DialogType.INPUT, self._main_window, bq_name)
if response == Gtk.ResponseType.CANCEL:
return
@@ -3930,14 +3963,17 @@ class Application(Gtk.Application):
self.show_error_message(get_message("A bouquet with that name exists!"))
return
model.set_value(itr, 0, response)
model.set_value(itr, Column.BQ_NAME, response)
if not model.iter_parent(itr):
return
old_bq_name = f"{bq_name}:{bq_type}"
self._bouquets[bq] = self._bouquets.pop(old_bq_name)
self._bq_file[bq] = self._bq_file.pop(old_bq_name, None)
self._current_bq_name = response
self._bq_name_label.set_text(self._current_bq_name)
self._bq_selected = bq
# services with extra names for the bouquet
# Services with extra names for the bouquet.
ext_bq = self._extra_bouquets.get(old_bq_name, None)
if ext_bq:
self._extra_bouquets[bq] = ext_bq
@@ -4223,8 +4259,7 @@ class Application(Gtk.Application):
self.show_error_message("No bouquets config is loaded. Load or create a new config!")
return
gen_bouquets(self._services_view, self._bouquets_view, self._main_window, g_type, self._s_type,
self.append_bouquet)
gen_bouquets(self, g_type)
# ***************** Alternatives ********************* #

View File

@@ -39,12 +39,12 @@ import os
import re
import shutil
import unicodedata
from collections import defaultdict
from functools import lru_cache
from itertools import groupby
from pathlib import Path
from urllib.parse import unquote
from gi.repository import GdkPixbuf, GLib
from gi.repository import GdkPixbuf, GLib, Gio
from app.eparser import Service
from app.eparser.ecommons import Flag, BouquetService, Bouquet, BqType
@@ -546,13 +546,13 @@ def remove_picons(settings, picon_ids, picons):
shutil.move(src, backup_path + p_id)
def is_only_one_item_selected(paths, transient):
def is_only_one_item_selected(paths, app):
if len(paths) > 1:
show_dialog(DialogType.ERROR, transient, "Please, select only one item!")
app.show_error_message("Please, select only one item!")
return False
if not paths:
show_dialog(DialogType.ERROR, transient, "No selected item!")
app.show_error_message("No selected item!")
return False
return True
@@ -565,6 +565,19 @@ def get_picon_pixbuf(path, size=32):
pass # NOP
def get_pixbuf_from_data(img_data, w=48, h=32):
if img_data:
f = Gio.MemoryInputStream.new_from_data(img_data)
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, w, h, True, None)
def get_pixbuf_at_scale(path, width, height, p_ratio):
try:
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, width, height, p_ratio)
except GLib.GError:
pass
@lru_cache(50)
def get_picon_file_name(service_name):
""" Returns picon file name by service name. """
@@ -574,44 +587,89 @@ def get_picon_file_name(service_name):
# ***************** Bouquets ********************* #
def gen_bouquets(view, bq_view, transient, gen_type, s_type, callback):
def gen_bouquets(app, gen_type):
""" Auto-generate and append list of bouquets. """
model, paths = view.get_selection().get_selected_rows()
single_types = (BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE)
if gen_type in single_types:
if not is_only_one_item_selected(paths, transient):
return
model, paths = app.services_view.get_selection().get_selected_rows()
single_types = {BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE}
if gen_type in single_types and not is_only_one_item_selected(paths, app):
return
fav_id_index = Column.SRV_FAV_ID
index = Column.SRV_TYPE
if gen_type in (BqGenType.PACKAGE, BqGenType.EACH_PACKAGE):
index = Column.SRV_PACKAGE
elif gen_type in (BqGenType.SAT, BqGenType.EACH_SAT):
index = Column.SRV_POS
# Splitting services [caching] by column value.
s_data = defaultdict(list)
for row in model:
s_data[row[index]].append(BouquetService(None, BqServiceType.DEFAULT, row[fav_id_index], 0))
ids = {row[Column.SRV_FAV_ID] for row in model}
services = [v for k, v in app.current_services.items() if k in ids]
bq_type = BqType.BOUQUET.value if s_type is SettingsType.NEUTRINO_MP else BqType.TV.value
bq_index = 0 if s_type is SettingsType.ENIGMA_2 else 1
bq_root_iter = bq_view.get_model().get_iter(bq_index)
srv = Service(*model[paths][:Column.SRV_TOOLTIP])
cond = srv.package if gen_type is BqGenType.PACKAGE else srv.pos if gen_type is BqGenType.SAT else srv.service_type
bq_view.expand_row(Gtk.TreePath(bq_index), 0)
if gen_type is BqGenType.TYPE and cond == "Data":
msg = f"{get_message('Selected type:')} '{cond}'\n\n{get_message('Are you sure?')}"
if show_dialog(DialogType.QUESTION, app.app_window, msg) != Gtk.ResponseType.OK:
return
def grouper(s):
data = s[index]
return data if data else "None"
services = {k: list(v) for k, v in groupby(sorted(services, key=grouper), key=grouper)}
bq_view = app.bouquets_view
bq_type = BqType.TV.value if app.is_enigma else BqType.BOUQUET.value
bq_index = 0 if app.is_enigma else 1
bq_root_iter = bq_view.get_model().get_iter(bq_index)
bq_names = get_bouquets_names(bq_view.get_model())
if gen_type in single_types:
if cond in bq_names:
show_dialog(DialogType.ERROR, transient, "A bouquet with that name exists!")
else:
callback(Bouquet(cond, bq_type, s_data.get(cond)), bq_root_iter)
app.show_error_message("A bouquet with that name exists!")
return
bq_services = get_services_type_groups(services.get(cond, []))
if app.is_enigma:
if srv.service_type == "Radio":
bq_index = 1
bq_type = BqType.RADIO.value
bq_root_iter = bq_view.get_model().get_iter(bq_index)
bq_view.expand_row(Gtk.TreePath(bq_index), 1)
bq_services = bq_services.get("Radio", [])
else:
bq_view.expand_row(Gtk.TreePath(bq_index), 0)
bq_services = bq_services.get("Data" if srv.service_type == "Data" else "TV", [])
app.append_bouquet(Bouquet(cond, bq_type, get_bouquet_services(bq_services)), bq_root_iter)
else:
bq_view.expand_row(Gtk.TreePath(bq_index), 0)
# We add a bouquet only if the given name is missing [keys - names]!
for name in sorted(s_data.keys() - bq_names):
callback(Bouquet(name, BqType.TV.value, s_data.get(name)), bq_root_iter)
if gen_type is BqGenType.EACH_SAT:
bq_names = sorted(services.keys() - bq_names, key=get_pos_num, reverse=True)
else:
bq_names = sorted(services.keys() - bq_names)
tv_bqs = []
radio_bqs = []
for n in bq_names:
bqs = services.get(n, [])
# TV and Radio separation.
bq_grp = get_services_type_groups(bqs)
tv_bq = bq_grp.get("TV", [])
tv_bqs.append(Bouquet(n, BqType.TV.value, get_bouquet_services(tv_bq))) if tv_bq else None
radio_bq = bq_grp.get("Radio", [])
radio_bqs.append(Bouquet(n, BqType.RADIO.value, get_bouquet_services(radio_bq))) if radio_bq else None
[app.append_bouquet(b, bq_root_iter) for b in tv_bqs]
if app.is_enigma:
bq_root_iter = bq_view.get_model().get_iter(bq_index + 1)
bq_view.expand_row(Gtk.TreePath(bq_index + 1), 0)
[app.append_bouquet(b, bq_root_iter) for b in radio_bqs]
def get_bouquet_services(services):
services.sort(key=lambda s: s.service)
return [BouquetService(None, BqServiceType.DEFAULT, s.fav_id, 0) for s in services]
def get_bouquets_names(model):
@@ -627,12 +685,28 @@ def get_bouquets_names(model):
return bouquets_names
def get_services_type_groups(services):
""" Returns services grouped by main types [TV, Radio, Data]. -> dict """
def type_grouper(s):
s_type = s.service_type
if s_type == "Data":
return s_type
elif s_type == "Radio":
return s_type
else:
return "TV"
return {k: list(v) for k, v in groupby(sorted(services, key=type_grouper), key=type_grouper)}
# ***************** Others ********************* #
def copy_reference(view, app):
""" Copying picon id to clipboard. """
model, paths = view.get_selection().get_selected_rows()
if not is_only_one_item_selected(paths, app.app_window):
if not is_only_one_item_selected(paths, app):
return
target = app.get_target_view(view)
@@ -759,7 +833,7 @@ def get_iptv_data(fav_id):
data, sep, desc = fav_id.partition("#DESCRIPTION")
data = data.split(":")
if len(data) < 11:
return None, None, desc
return None, desc
return ":".join(data[:10]), unquote(data[10].strip())

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -33,7 +33,7 @@ from enum import Enum
from pathlib import Path
from urllib.parse import urlparse, unquote
from gi.repository import GLib, GdkPixbuf, Gio
from gi.repository import GLib
from app.commons import run_idle, run_task, run_with_delay, log
from app.connections import upload_data, DownloadType, download_data, remove_picons
@@ -43,7 +43,7 @@ from app.tools.picons import (PiconsParser, parse_providers, Provider, convert_t
from app.tools.satellites import SatellitesParser, SatelliteSource
from .dialogs import show_dialog, DialogType, get_message, get_builder, get_chooser_dialog
from .main_helper import (scroll_to, on_popup_menu, get_base_model, set_picon, get_picon_pixbuf, get_picon_dialog,
get_picon_file_name)
get_picon_file_name, get_pixbuf_from_data, get_pixbuf_at_scale)
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey, Page, ViewTarget
@@ -52,8 +52,8 @@ class PiconManager(Gtk.Box):
LYNG_SAT = "lyngsat"
PICON_CZ = "piconcz"
def __init__(self, app, settings, picon_ids, sat_positions, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, app, settings, picon_ids, sat_positions, **kwargs):
super().__init__(**kwargs)
self._app = app
self._app.connect("data-receive", self.on_download)
@@ -291,7 +291,7 @@ class PiconManager(Gtk.Box):
yield True
def picon_data_func(self, column, renderer, model, itr, data):
renderer.set_property("pixbuf", self.get_pixbuf_at_scale(model.get_value(itr, 2), 72, 48, True))
renderer.set_property("pixbuf", get_pixbuf_at_scale(model.get_value(itr, 2), 72, 48, True))
def update_picons_from_file(self, view, uri):
""" Adds picons in the view on dragging from file system. """
@@ -303,18 +303,12 @@ class PiconManager(Gtk.Box):
model = get_base_model(view.get_model())
if path.is_file():
p = self.get_pixbuf_at_scale(f_path, 72, 48, True)
p = get_pixbuf_at_scale(f_path, 72, 48, True)
if p:
model.append((p, path.name, f_path))
elif path.is_dir():
self.update_picons_data(view, f_path)
def get_pixbuf_at_scale(self, path, width, height, p_ratio):
try:
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, width, height, p_ratio)
except GLib.GError:
pass
# ***************** Drag-and-drop ********************* #
def init_drag_and_drop(self):
@@ -386,7 +380,7 @@ class PiconManager(Gtk.Box):
paths = {r[1]: r.iter for r in dest_model}
for p_path in picons:
p = self.get_pixbuf_at_scale(p_path, 72, 48, True)
p = get_pixbuf_at_scale(p_path, 72, 48, True)
if p:
p_name = Path(p_path).name
itr = paths.get(p_name, None)
@@ -424,8 +418,8 @@ class PiconManager(Gtk.Box):
shutil.copy(src, dst)
for row in get_base_model(self._picons_dest_view.get_model()):
if name == row[1]:
row[0] = self.get_pixbuf_at_scale(row[-1], 72, 48, True)
img.set_from_pixbuf(self.get_pixbuf_at_scale(row[-1], 100, 60, True))
row[0] = get_pixbuf_at_scale(row[-1], 72, 48, True)
img.set_from_pixbuf(get_pixbuf_at_scale(row[-1], 100, 60, True))
gen = self.update_picon_in_lists(dst, fav_id)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
@@ -602,10 +596,10 @@ class PiconManager(Gtk.Box):
if logo_url:
pix_data = self._picon_cz_downloader.get_logo_data(logo_url)
if pix_data:
pix = self.get_pixbuf(pix_data)
pix = get_pixbuf_from_data(pix_data)
model.set_value(itr, 0, pix if pix else TV_ICON)
size = self._settings.tooltip_logo_size
tooltip.set_icon(self.get_pixbuf(pix_data, size, size))
tooltip.set_icon(get_pixbuf_from_data(pix_data, size, size))
else:
self.update_logo_data(itr, model, logo_url)
tooltip.set_text(model.get_value(itr, 1))
@@ -616,7 +610,7 @@ class PiconManager(Gtk.Box):
def update_logo_data(self, itr, model, url):
pix_data = self._picon_cz_downloader.get_provider_logo(url)
if pix_data:
pix = self.get_pixbuf(pix_data)
pix = get_pixbuf_from_data(pix_data)
GLib.idle_add(model.set_value, itr, 0, pix if pix else TV_ICON)
@run_idle
@@ -699,20 +693,15 @@ class PiconManager(Gtk.Box):
def append_providers(self, providers, model):
if self._download_src is self.DownloadSource.LYNG_SAT:
for p in providers:
model.append(p._replace(logo=self.get_pixbuf(p.logo) if p.logo else TV_ICON))
model.append(p._replace(logo=get_pixbuf_from_data(p.logo) if p.logo else TV_ICON))
elif self._download_src is self.DownloadSource.PICON_CZ:
for p in providers:
logo_data = self._picon_cz_downloader.get_logo_data(p.ssid)
model.append(p._replace(logo=self.get_pixbuf(logo_data) if logo_data else TV_ICON))
model.append(p._replace(logo=get_pixbuf_from_data(logo_data) if logo_data else TV_ICON))
self.update_receive_button_state()
GLib.idle_add(self._satellite_label.set_visible, True)
def get_pixbuf(self, img_data, w=48, h=32):
if img_data:
f = Gio.MemoryInputStream.new_from_data(img_data)
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, w, h, True, None)
def on_receive(self, item):
if self._is_downloading:
self._app.show_error_message("The task is already running!")
@@ -949,7 +938,7 @@ class PiconManager(Gtk.Box):
self.update_picon_info(name, path, srv)
def update_picon_info(self, name=None, path=None, srv=None):
self._picon_info_image.set_from_pixbuf(self.get_pixbuf_at_scale(path, 100, 60, True) if path else None)
self._picon_info_image.set_from_pixbuf(get_pixbuf_at_scale(path, 100, 60, True) if path else None)
self._picon_info_label.set_text(self.get_service_info(srv))
self._current_picon_info = (name, srv.fav_id) if srv else None

View File

@@ -30,6 +30,8 @@ import concurrent.futures
import os
import re
import time
from collections import OrderedDict
from itertools import groupby
from math import fabs
from gi.repository import GLib
@@ -38,11 +40,13 @@ from app.commons import run_idle, run_task, log
from app.eparser import Satellite, Transponder
from app.eparser.ecommons import (PLS_MODE, get_key_by_value, POLARIZATION, FEC, SYSTEM, MODULATION, Terrestrial, Cable,
T_SYSTEM, BANDWIDTH, CONSTELLATION, T_FEC, GUARD_INTERVAL, TRANSMISSION_MODE,
HIERARCHY, Inversion, C_MODULATION, FEC_DEFAULT, TerTransponder, CableTransponder)
from app.settings import USE_HEADER_BAR
HIERARCHY, Inversion, C_MODULATION, FEC_DEFAULT, TerTransponder, CableTransponder,
Bouquet, BouquetService, BqServiceType, Bouquets, BqType)
from app.eparser.satxml import get_pos_str
from app.settings import USE_HEADER_BAR, Settings, CONFIG_PATH
from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser
from ..dialogs import show_dialog, DialogType, get_message, get_builder
from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu
from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu, get_services_type_groups
from ..search import SearchProvider
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HeaderBar
@@ -408,14 +412,13 @@ class UpdateDialog:
self._settings = settings
self._download_task = False
self._parser = None
self._size_name = f"{'_'.join(re.findall('[A-Z][^A-Z]*', self.__class__.__name__))}_window_size".lower()
self._selected_satellites = set()
builder = get_builder(f"{UI_RESOURCES_PATH}xml{os.sep}update.glade", handlers)
self._window = builder.get_object("satellites_update_window")
self._window.set_transient_for(transient)
if title:
self._window.set_title(title)
self._window.set_title(title if title else "")
self._transponder_paned = builder.get_object("sat_update_tr_paned")
self._sat_view = builder.get_object("sat_update_tree_view")
@@ -435,6 +438,8 @@ class UpdateDialog:
self._sat_view.bind_property("sensitive", self._source_box, "sensitive")
self._sat_view.bind_property("sensitive", self._receive_button, "sensitive")
self._receive_button.bind_property("visible", update_button, "visible")
self._left_action_box = builder.get_object("sat_update_left_action_box")
self._right_action_box = builder.get_object("sat_update_right_action_box")
# Filter
self._filter_bar = builder.get_object("sat_update_filter_bar")
self._from_pos_button = builder.get_object("from_pos_button")
@@ -456,6 +461,12 @@ class UpdateDialog:
builder.get_object("sat_update_search_down_button"),
builder.get_object("sat_update_search_up_button"))
builder.get_object("sat_update_find_button").connect("toggled", search_provider.on_search_toggled)
# Satellite lists init on dialog start.
self._sat_view.connect("realize", self.on_update_satellites_list)
# Options.
self._general_options_box = builder.get_object("general_options_box")
self._save_sat_selection_switch = builder.get_object("save_sat_selection_switch")
self._skip_c_band_switch = builder.get_object("skip_c_band_switch")
if self._settings.use_header_bar:
header_bar = HeaderBar()
@@ -463,15 +474,23 @@ class UpdateDialog:
header_box = builder.get_object("satellites_update_header_box")
header_box.remove(self._source_box)
header_bar.pack_start(self._source_box)
action_box = builder.get_object("sat_update_left_action_box")
header_box.remove(action_box)
header_bar.pack_start(action_box)
action_box = builder.get_object("sat_update_right_action_box")
header_box.remove(action_box)
header_bar.pack_end(action_box)
header_box.remove(self._left_action_box)
header_bar.pack_start(self._left_action_box)
header_box.remove(self._right_action_box)
header_bar.pack_end(self._right_action_box)
self._window.set_titlebar(header_bar)
window_size = self._settings.get(self._size_name)
# Dialog settings.
self._dialog_name = f"{'_'.join(re.findall('[A-Z][^A-Z]*', self.__class__.__name__))}".lower()
self._dialog_settings = self._settings.get(self._dialog_name, {})
self._source_box.set_active(self._dialog_settings.get("source", 1))
self._save_sat_selection_switch.set_active(self._dialog_settings.get("save_sat_selection", False))
self._skip_c_band_switch.set_active(self._dialog_settings.get("skip_c_band", False))
if self._save_sat_selection_switch.get_active():
self._selected_satellites.update(self.get_selected_satellites())
window_size = self._dialog_settings.get("window_size", None)
if window_size:
self._window.resize(*window_size)
@@ -497,11 +516,11 @@ class UpdateDialog:
self.is_download = True
self._sat_view.set_sensitive(False)
src = self._source_box.get_active()
if not self._parser:
self._parser = SatellitesParser()
self.get_sat_list(src, self.append_satellites)
self.get_sat_list(self._source_box.get_active(), self.append_satellites)
def clear_data(self):
get_base_model(self._sat_view.get_model()).clear()
@@ -526,11 +545,14 @@ class UpdateDialog:
@run_idle
def append_satellites(self, sats):
model = get_base_model(self._sat_view.get_model())
for sat in sats:
model.append(sat)
itr = model.append(sat)
model[itr][-1] = sat[-2] in self._selected_satellites
self._sat_view.set_sensitive(True)
self._satellites_count_label.set_text(str(len(model)))
self.update_receive_button_state(self._filter_model)
@run_idle
def on_receive_data(self, item):
@@ -625,10 +647,33 @@ class UpdateDialog:
itr = self._filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(model.get_iter(path)))
self._filter_model.get_model().set_value(itr, 4, select)
if self._save_sat_selection_switch.get_active():
sat = model[path][-2]
self._selected_satellites.add(sat) if select else self._selected_satellites.discard(sat)
def on_quit(self, window, event):
self._settings.add(self._size_name, window.get_size())
self.save_settings()
self.is_download = False
def save_settings(self):
self._dialog_settings["window_size"] = self._window.get_size()
self._dialog_settings["source"] = self._source_box.get_active()
self._dialog_settings["save_sat_selection"] = self._save_sat_selection_switch.get_active()
self._dialog_settings["skip_c_band"] = self._skip_c_band_switch.get_active()
self._settings.add(self._dialog_name, self._dialog_settings)
self.save_selected_satellites()
def get_selected_satellites(self):
""" Returns selected satellites set from the last session. """
c_file = f"{CONFIG_PATH}{self._dialog_name}_satellites"
return Settings.get_settings(c_file, default_settings=[])
def save_selected_satellites(self):
""" Saves current selected satellites to a file. """
if self._save_sat_selection_switch.get_active():
c_file = f"{CONFIG_PATH}{self._dialog_name}_satellites"
Settings.write_settings(list(self._selected_satellites), config_file=c_file)
class SatellitesUpdateDialog(UpdateDialog):
""" Dialog for update satellites from the Web. """
@@ -638,6 +683,16 @@ class SatellitesUpdateDialog(UpdateDialog):
self._main_model = main_model
self._source_box.connect("changed", self.on_update_satellites_list)
# Options.
self._merge_sat_switch = Gtk.Switch(active=self._dialog_settings.get("merge_satellites", False))
self._merge_sat_switch.connect("state-set", lambda b, s: self._dialog_settings.update({"merge_satellites": s}))
box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL)
box.pack_start(Gtk.Label(get_message("Merge satellites by positions")), False, True, 0)
box.pack_end(self._merge_sat_switch, False, True, 0)
self._general_options_box.pack_start(box, True, True, 0)
self._general_options_box.show_all()
self._skip_c_band_switch.get_parent().set_visible(False)
@run_idle
def on_receive_data(self, item):
@@ -653,6 +708,7 @@ class SatellitesUpdateDialog(UpdateDialog):
self.update_log_visibility()
model = self._sat_view.get_model()
start = time.time()
_len = 75
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
text = "Processing: {}\n"
@@ -672,10 +728,39 @@ class SatellitesUpdateDialog(UpdateDialog):
appender.send(text.format(data[0]))
sats.append(data)
appender.send("-" * 75 + "\n")
appender.send("-" * _len + "\n")
sat_count = len(sats)
sats = {s[0]: s for s in sats} # key = name, v = satellite
if self._merge_sat_switch.get_active():
def grouper(sat):
try:
return int(sat.position)
except ValueError:
pass
return 0
sat_groups = groupby(sorted(sats, key=grouper, reverse=True), key=grouper)
sats = {}
for pos, satellites in sat_groups:
satellites = list(satellites)
if len(satellites) > 1:
position = get_pos_str(pos)
appender.send(f"Merging satellites for position: {position}\n")
names = []
transponders = []
for s in satellites:
names.append(s.name.lstrip(position).strip().split())
transponders.extend(s.transponders)
transponders.sort(key=lambda t: int(t.frequency))
sat = Satellite(self.get_grouped_satellite_name(names, position), "0", str(pos), transponders)
sats[sat.name] = sat
else:
sat = satellites.pop()
sats[sat.name] = sat
appender.send("-" * _len + "\n")
else:
sats = {s.name: s for s in sats} # key = name, v = satellite
for row in self._main_model:
pos = row[0]
@@ -688,11 +773,39 @@ class SatellitesUpdateDialog(UpdateDialog):
appender.send(f"Adding satellite: {s.name}\n")
self.append_satellite(s)
appender.send("-" * 75 + "\n")
appender.send("-" * _len + "\n")
appender.send(f"Consumed: {time.time() - start:0.0f}s, {sat_count} satellites received.\n")
appender.close()
self.is_download = False
def get_grouped_satellite_name(self, sat_names, pos):
""" Forms name for merged satellites. """
def name_grouper(nd):
if nd:
return nd[0]
return ""
name_groups = groupby(sorted(sat_names, key=name_grouper), key=name_grouper)
names = []
for s, s_names in name_groups:
tk = set()
name = s
for i, n_data in enumerate(s_names):
if i == 0:
name = " ".join(n_data)
tk.update(n_data)
else:
for n in n_data:
if n in tk:
continue
name = f"{name}/{n}"
tk.add(n)
names.append(name)
return f"{pos} {' & '.join(names)}"
@run_idle
def append_satellite(self, sat):
self._main_model.append(sat)
@@ -701,16 +814,16 @@ class SatellitesUpdateDialog(UpdateDialog):
class ServicesUpdateDialog(UpdateDialog):
""" Dialog for updating services from the Web. """
def __init__(self, transient, settings, callback):
super().__init__(transient=transient, settings=settings, title="Services update")
def __init__(self, app):
super().__init__(transient=app.app_window, settings=app.app_settings, title="Services update")
self._callback = callback
self._callback = app.on_import_data_from_web
self._satellite_paths = {}
self._transponders = {}
self._services = {}
self._selected_transponders = set()
self._services_parser = ServicesParser(source=SatelliteSource.LYNGSAT)
# Transponder view popup menu
# Transponder view popup menu.
tr_popup_menu = Gtk.Menu()
select_all_item = Gtk.ImageMenuItem.new_from_stock("gtk-select-all")
select_all_item.connect("activate", lambda w: self.update_transponder_selection(True))
@@ -728,6 +841,24 @@ class ServicesUpdateDialog(UpdateDialog):
self._transponder_paned.set_visible(True)
self._source_box.connect("changed", self.on_update_satellites_list)
self._source_box.connect("changed", self.on_source_changed)
# Options for KingOfSat source.
self._kos_bq_groups_switch = Gtk.Switch(active=self._dialog_settings.get("kos_bq_groups", False))
self._kos_bq_groups_switch.connect("state-set", lambda b, s: self._dialog_settings.update({"kos_bq_groups": s}))
self._kos_bq_lang_switch = Gtk.Switch(active=self._dialog_settings.get("kos_bq_lang", False))
self._kos_bq_lang_switch.connect("state-set", lambda b, s: self._dialog_settings.update({"kos_bq_lang": s}))
self._kos_options_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5, margin_top=5)
box.pack_start(Gtk.Label(get_message("Create Category bouquets")), False, True, 0)
box.pack_end(self._kos_bq_groups_switch, False, True, 0)
self._kos_options_box.add(box)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5, margin_bottom=5)
box.pack_start(Gtk.Label(get_message("Create Regional bouquets")), False, True, 0)
box.pack_end(self._kos_bq_lang_switch, False, True, 0)
self._kos_options_box.add(box)
self._kos_options_box.connect("realize", self.on_source_changed)
self._general_options_box.pack_start(self._kos_options_box, True, True, 0)
self._general_options_box.show_all()
@run_idle
def on_receive_data(self, item):
@@ -737,6 +868,14 @@ class ServicesUpdateDialog(UpdateDialog):
self.receive_services()
def on_source_changed(self, item):
is_kos = self._source_box.get_active_id() == SatelliteSource.KINGOFSAT.name
self._kos_options_box.set_sensitive(is_kos)
if not is_kos:
self._kos_bq_groups_switch.set_active(False)
self._kos_bq_lang_switch.set_active(False)
self._kos_options_box.set_tooltip_text(None if is_kos else get_message("KingOfSat only!"))
@run_task
def receive_services(self):
self.is_download = True
@@ -805,6 +944,7 @@ class ServicesUpdateDialog(UpdateDialog):
log(f"Getting services error: {e} [{t_names.get(futures[future])}]")
appender.send("-" * 75 + "\n")
services = OrderedDict({s.fav_id: s for s in services}).values()
appender.send(f"Consumed: {time.time() - start:0.0f}s, {len(services)} services received.")
try:
@@ -815,10 +955,47 @@ class ServicesUpdateDialog(UpdateDialog):
except ValueError as e:
log(f"ServicesUpdateDialog [on receive data] error: {e}")
else:
self._callback(srvs)
bouquets = None
if self._source_box.get_active_id() == SatelliteSource.KINGOFSAT.name:
bouquets = self.get_bouquets([srv._replace(fav_id=srvs[i].fav_id) for i, srv in enumerate(services)])
def c_filter(s):
try:
return int(s.freq) > 10000
except ValueError:
return False
self._callback(filter(c_filter, srvs) if self._skip_c_band_switch.get_active() else srvs, bouquets)
self.is_download = False
def get_bouquets(self, services):
type_groups = get_services_type_groups(services)
tv_bouquets, radio_bouquets = [], []
tv_services = sorted(type_groups.get("TV", []), key=lambda s: s.service)
rd_services = sorted(type_groups.get("Radio", []), key=lambda s: s.service)
no_lb = "No Category"
if self._kos_bq_groups_switch.get_active():
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[4] or no_lb)
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[4] or no_lb, bq_type=BqType.RADIO.value)
if self._kos_bq_lang_switch.get_active():
lb = "" if no_lb in {b.name for b in tv_bouquets} else "No Region"
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[5] or lb)
lb = "" if no_lb in {b.name for b in radio_bouquets} else "No Region"
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[5] or lb, bq_type=BqType.RADIO.value)
return Bouquets("", BqType.TV.value, tv_bouquets), Bouquets("", BqType.RADIO.value, radio_bouquets)
def gen_bouquet_group(self, services, bouquets, grouper, bq_type=BqType.TV.value):
""" Generates bouquets depending on <grouper>. """
s_type = BqServiceType.DEFAULT
[bouquets.append(Bouquet(name=g[0], type=bq_type,
services=[BouquetService(None, s_type, s.fav_id, 0) for s in g[1]])) for g in
groupby(sorted(services, key=grouper), key=grouper) if g[0]]
@run_task
def get_sat_list(self, src, callback):
sat_src = SatelliteSource.LYNGSAT
@@ -831,10 +1008,9 @@ class ServicesUpdateDialog(UpdateDialog):
self.is_download = False
def on_satellite_toggled(self, toggle, path):
model = self._sat_view.get_model()
self.update_state(model, path, not toggle.get_active())
self.update_receive_button_state(self._filter_model)
super().on_satellite_toggled(toggle, path)
model = self._sat_view.get_model()
url = model.get_value(model.get_iter(path), 3)
selected = toggle.get_active()
transponders = self._transponders.get(url, None)

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -37,9 +37,9 @@ from app.eparser import get_satellites, write_satellites, Satellite, Transponder
from app.eparser.ecommons import (POLARIZATION, FEC, SYSTEM, MODULATION, T_SYSTEM, BANDWIDTH, CONSTELLATION, T_FEC,
GUARD_INTERVAL, TRANSMISSION_MODE, HIERARCHY, Inversion, FEC_DEFAULT, C_MODULATION,
Terrestrial, Cable, CableTransponder, TerTransponder)
from app.eparser.satxml import get_terrestrial, get_cable, write_terrestrial, write_cable
from .dialogs import SatelliteDialog, SatellitesUpdateDialog, TerrestrialDialog, CableDialog, SatTransponderDialog, \
CableTransponderDialog, TerTransponderDialog
from app.eparser.satxml import get_terrestrial, get_cable, write_terrestrial, write_cable, get_pos_str
from .dialogs import (SatelliteDialog, SatellitesUpdateDialog, TerrestrialDialog, CableDialog, SatTransponderDialog,
CableTransponderDialog, TerTransponderDialog)
from ..dialogs import show_dialog, DialogType, get_chooser_dialog, get_message, get_builder
from ..main_helper import move_items, on_popup_menu, scroll_to
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, MOVE_KEYS, KeyboardKey, MOD_MASK, Page
@@ -56,8 +56,8 @@ class SatellitesTool(Gtk.Box):
def __str__(self):
return self.value
def __init__(self, app, settings, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, app, settings, **kwargs):
super().__init__(**kwargs)
self._app = app
self._app.connect("data-save", self.on_save)
@@ -162,8 +162,7 @@ class SatellitesTool(Gtk.Box):
def sat_pos_func(self, column, renderer, model, itr, data):
""" Converts and sets the satellite position value to a readable format. """
pos = int(model.get_value(itr, 2))
renderer.set_property("text", f"{abs(pos / 10):0.1f}{'W' if pos < 0 else 'E'}")
renderer.set_property("text", get_pos_str(int(model.get_value(itr, 2))))
def sat_pol_func(self, column, renderer, model, itr, data):
renderer.set_property("text", POLARIZATION.get(model.get_value(itr, 2), None))

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2
<!-- Generated with glade 3.38.2
The MIT License (MIT)
@@ -65,19 +65,19 @@ Author: Dmitriy Yefremov
</object>
<object class="GtkImage" id="popup_menu_add_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="stock">gtk-add</property>
</object>
<object class="GtkMenu" id="popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImageMenuItem" id="add_sat_popup_menu_item">
<property name="label" translatable="yes">Add</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="image">popup_menu_add_image</property>
<property name="use_stock">False</property>
<property name="use-stock">False</property>
<signal name="activate" handler="on_add" swapped="no"/>
<accelerator key="Insert" signal="activate"/>
</object>
@@ -85,16 +85,16 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkSeparatorMenuItem" id="popup_sat_menu_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="edit_sat_popup_menu_item">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<property name="can-focus">False</property>
<property name="use-underline">True</property>
<property name="use-stock">True</property>
<signal name="activate" handler="on_edit" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="Primary"/>
</object>
@@ -102,16 +102,16 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkSeparatorMenuItem" id="popup_sat_menu_separator_2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="remove_sat_popup_menu_item">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<property name="can-focus">False</property>
<property name="use-underline">True</property>
<property name="use-stock">True</property>
<signal name="activate" handler="on_remove" swapped="no"/>
<accelerator key="Delete" signal="activate"/>
</object>
@@ -119,19 +119,19 @@ Author: Dmitriy Yefremov
</object>
<object class="GtkImage" id="popup_menu_add_image_2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="stock">gtk-add</property>
</object>
<object class="GtkMenu" id="transponder_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImageMenuItem" id="add_tr_popup_menu_item">
<property name="label" translatable="yes">Add</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="image">popup_menu_add_image_2</property>
<property name="use_stock">False</property>
<property name="use-stock">False</property>
<signal name="activate" handler="on_transponder_add" swapped="no"/>
<accelerator key="Insert" signal="activate"/>
</object>
@@ -139,16 +139,16 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkSeparatorMenuItem" id="popup_tr_menu_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="edit_tr_popup_menu_item">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<property name="can-focus">False</property>
<property name="use-underline">True</property>
<property name="use-stock">True</property>
<signal name="activate" handler="on_transponder_edit" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="Primary"/>
</object>
@@ -156,16 +156,16 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkSeparatorMenuItem" id="popup_tr_menu_separator_2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="remove_tr_popup_menu_item">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<property name="can-focus">False</property>
<property name="use-underline">True</property>
<property name="use-stock">True</property>
<signal name="activate" handler="on_transponder_remove" swapped="no"/>
<accelerator key="Delete" signal="activate"/>
</object>
@@ -255,45 +255,58 @@ Author: Dmitriy Yefremov
</object>
<object class="GtkPaned" id="main_paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wide_handle">True</property>
<property name="can-focus">True</property>
<property name="wide-handle">True</property>
<child>
<object class="GtkFrame" id="editor_sat_frame">
<property name="width_request">360</property>
<property name="width-request">360</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.49000000953674316</property>
<property name="shadow_type">in</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="editor_sat_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="editor_header_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">5</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">2</property>
<property name="margin-bottom">5</property>
<property name="spacing">5</property>
<child type="center">
<object class="GtkStackSwitcher" id="stack_switcher">
<property name="name">header-stack-switcher</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="stack">sat_stack</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="add_header_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Add</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Add</property>
<signal name="clicked" handler="on_add" swapped="no"/>
<child>
<object class="GtkImage" id="add_satellite_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-new-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">document-new-symbolic</property>
</object>
</child>
</object>
@@ -306,15 +319,15 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkButton" id="update_header_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Update</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Update</property>
<signal name="clicked" handler="on_update" swapped="no"/>
<child>
<object class="GtkImage" id="update_header_button_img">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">emblem-synchronizing-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">emblem-synchronizing-symbolic</property>
</object>
</child>
</object>
@@ -324,19 +337,6 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child type="center">
<object class="GtkStackSwitcher" id="stack_switcher">
<property name="visible">True</property>
<property name="name">header-stack-switcher</property>
<property name="can_focus">False</property>
<property name="stack">sat_stack</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -347,26 +347,26 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkStack" id="sat_stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<signal name="notify::visible-child-name" handler="on_visible_page" swapped="no"/>
<child>
<object class="GtkBox" id="satellite_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Satellites</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Satellites</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="satellite_view_scrolled">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="satellite_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">satellite_model</property>
<property name="rubber_banding">True</property>
<property name="activate_on_single_click">True</property>
<property name="rubber-banding">True</property>
<property name="activate-on-single-click">True</property>
<signal name="button-press-event" handler="on_button_press" object="popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_satellite_selection" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
@@ -393,7 +393,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkTreeViewColumn" id="sat_pos_column">
<property name="fixed_width">85</property>
<property name="fixed-width">85</property>
<property name="title" translatable="yes">Pos</property>
<property name="alignment">0.49000000953674316</property>
<child>
@@ -417,17 +417,17 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="sat_status_box">
<property name="height_request">26</property>
<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="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="sat_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</property>
</object>
<packing>
<property name="expand">False</property>
@@ -438,9 +438,9 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="sat_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">4</property>
<property name="width-chars">4</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -465,22 +465,22 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="terrestrial_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Terrestrial</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Terrestrial</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="terrestrial_view_scrolled">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="terrestrial_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">terrestrial_model</property>
<property name="search_column">0</property>
<property name="rubber_banding">True</property>
<property name="activate_on_single_click">True</property>
<property name="search-column">0</property>
<property name="rubber-banding">True</property>
<property name="activate-on-single-click">True</property>
<signal name="button-press-event" handler="on_button_press" object="popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_terrestrial_selection" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
@@ -516,17 +516,17 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="ter_status_box">
<property name="height_request">26</property>
<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="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="ter_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</property>
</object>
<packing>
<property name="expand">False</property>
@@ -537,9 +537,9 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="ter_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">4</property>
<property name="width-chars">4</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -565,22 +565,22 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="cable_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Cable</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Cable</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="cable_view_scrolled">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="cable_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">cable_model</property>
<property name="search_column">0</property>
<property name="rubber_banding">True</property>
<property name="activate_on_single_click">True</property>
<property name="search-column">0</property>
<property name="rubber-banding">True</property>
<property name="activate-on-single-click">True</property>
<signal name="button-press-event" handler="on_button_press" object="popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_cable_selection" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
@@ -616,17 +616,17 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="cable_status_box">
<property name="height_request">26</property>
<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="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="cable_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</property>
</object>
<packing>
<property name="expand">False</property>
@@ -637,9 +637,9 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="cable_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">4</property>
<property name="width-chars">4</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -674,7 +674,7 @@ Author: Dmitriy Yefremov
<child type="label">
<object class="GtkLabel" id="editor_sat_frame_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">DVB</property>
</object>
</child>
@@ -687,39 +687,39 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkFrame" id="editor_transponder_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.49000000953674316</property>
<property name="shadow_type">in</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="transponders_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-top">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="editor_tr_header_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">15</property>
<property name="margin_right">15</property>
<property name="margin_top">2</property>
<property name="margin_bottom">5</property>
<property name="can-focus">False</property>
<property name="margin-left">15</property>
<property name="margin-right">15</property>
<property name="margin-top">2</property>
<property name="margin-bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkButton" id="transponder_add_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Add</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Add</property>
<signal name="clicked" handler="on_transponder_add" swapped="no"/>
<child>
<object class="GtkImage" id="add_transponder_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-new-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">document-new-symbolic</property>
</object>
</child>
</object>
@@ -742,25 +742,25 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkStack" id="transponders_stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkBox" id="sat_tr_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="sat_tr_view_scrolled">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="sat_tr_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">sat_tr_view_model</property>
<property name="search_column">0</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<property name="search-column">0</property>
<property name="rubber-banding">True</property>
<property name="enable-grid-lines">both</property>
<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">
@@ -771,7 +771,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="freq_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -788,7 +788,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="rate_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Rate</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -805,7 +805,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="pol_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Pol</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -822,7 +822,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="fec_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">FEC</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -839,7 +839,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="sys_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">System</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -856,7 +856,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="mod_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Mod</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -935,17 +935,17 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="sat_tr_status_box">
<property name="height_request">26</property>
<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="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="sat_tr_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</property>
</object>
<packing>
<property name="expand">False</property>
@@ -956,9 +956,9 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="sat_tr_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">4</property>
<property name="width-chars">4</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -983,21 +983,21 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="ter_tr_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="ter_tr_view_scrolled">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="ter_tr_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">ter_tr_view_model</property>
<property name="search_column">0</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<property name="search-column">0</property>
<property name="rubber-banding">True</property>
<property name="enable-grid-lines">both</property>
<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">
@@ -1008,7 +1008,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="ter_freq_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1025,7 +1025,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="ter_system_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">System</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1042,7 +1042,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="ter_bandwidth_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Bandwidth</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1059,7 +1059,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="ter_constellation_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Constellation</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1076,7 +1076,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="ter_rate_hp_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">SR (HP)</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1107,7 +1107,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="ter_guard_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Guard</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1177,17 +1177,17 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="ter_tr_status_box">
<property name="height_request">26</property>
<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="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="ter_tr_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1198,9 +1198,9 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="ter_tr_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">4</property>
<property name="width-chars">4</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -1226,21 +1226,21 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="cable_tr_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="cable_tr_view_scrolled">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="cable_tr_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">cable_tr_view_model</property>
<property name="search_column">0</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<property name="search-column">0</property>
<property name="rubber-banding">True</property>
<property name="enable-grid-lines">both</property>
<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">
@@ -1251,7 +1251,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="cable_freq_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1268,7 +1268,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="cable_rate_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Rate</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1285,7 +1285,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="cable_fec_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">FEC</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1302,7 +1302,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="cable_mod_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min-width">20</property>
<property name="title" translatable="yes">Mod</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
@@ -1327,17 +1327,17 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="cable_tr_status_box">
<property name="height_request">26</property>
<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="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="cable_tr_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1348,9 +1348,9 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="cable_tr_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">4</property>
<property name="width-chars">4</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -1385,7 +1385,7 @@ Author: Dmitriy Yefremov
<child type="label">
<object class="GtkLabel" id="editor_transponder_frame_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Transponder</property>
</object>
</child>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
#!/bin/bash
VER="3.4.2_Beta"
VER="3.6.2_Beta"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 3.4.2-Beta
Version: 3.6.2-Beta
Section: utils
Priority: optional
Architecture: all

View File

@@ -81,7 +81,7 @@ app = BUNDLE(coll,
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
'LSApplicationCategoryType': 'public.app-category.utilities',
'LSMinimumSystemVersion': '10.13',
'CFBundleShortVersionString': f"3.4.2.{BUILD_DATE} Beta",
'CFBundleShortVersionString': f"3.6.2.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2023, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false',
'NSHighResolutionCapable': 'true'

View File

@@ -47,6 +47,9 @@ class BaseExtension(metaclass=Singleton):
# The label that will be displayed in the "Tools" menu.
LABEL = "Base extension"
VERSION = "1.0"
# Additional flags.
EMBEDDED = False
SWITCHABLE = False
_LOGGER_NAME = "main_logger"
@@ -65,6 +68,10 @@ class BaseExtension(metaclass=Singleton):
"""
self.app.show_info_message(f"Hello from {self.__class__.__name__} class!")
def stop(self):
""" Stops (terminates) the task or the extension itself. """
self.log("Terminating a task...")
def log(self, message, level=logging.ERROR):
""" Shows log messages. """
logging.getLogger(self._LOGGER_NAME).log(level, f"[{self.__class__.__name__}] {message}")

View File

@@ -1446,3 +1446,45 @@ msgstr "Выдаліць дублікаты"
msgid "Removed"
msgstr "Выдалена"
msgid "Enables overwriting existing main list services."
msgstr "Улучае перазапіс існых сэрвісаў асноўнага спіса."
msgid "Enables skipping services import from lamedb."
msgstr "Улучае пропуск імпарту сэрвісаў з lamedb."
msgid "Bouquets data only"
msgstr "Толькі дадзеныя букетаў"
msgid "Create Category bouquets"
msgstr "Стварыць букеты па катэгорыях"
msgid "Create Regional bouquets"
msgstr "Стварыць букеты па рэгіёнах"
msgid "Skip C-band"
msgstr "Прапусціць C-дыяпазон"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Аўтаматычная ўсталёўка імя, абранага ў спісе букетаў."
msgid "Merge satellites by positions"
msgstr "Аб'яднаць спадарожнікі па пазіцыях"
msgid "Save satellite selection"
msgstr "Захоўваць выбар спадарожнікаў"
msgid "Extract direct links"
msgstr "Выняць простыя спасылкі"
msgid "URL prefix:"
msgstr "Префикс URL:"
msgid "Invalid prefix for the given URL!"
msgstr "Недапушчальны прэфікс для дадзенага URL!"
msgid "Alternate window title"
msgstr "Альтэрнатыўны загаловак акна"
msgid "Selected type:"
msgstr "Абраны тып:"

View File

@@ -1460,3 +1460,45 @@ msgstr "Duplikate entfernen"
msgid "Removed"
msgstr "Entfernt"
msgid "Enables overwriting existing main list services."
msgstr "Ermöglicht das Überschreiben vorhandener Hauptlistendienste."
msgid "Enables skipping services import from lamedb."
msgstr "Ermöglicht das Überspringen von Dienstimporten aus lamedb."
msgid "Bouquets data only"
msgstr "Nur Bouquets-Daten"
msgid "Create Category bouquets"
msgstr "Erstellen Kategories-Bouquets"
msgid "Create Regional bouquets"
msgstr "Erstellen Regionale-Bouquets"
msgid "Skip C-band"
msgstr "C-Band überspringen"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Stellen in der Bouquet-Liste ausgewählten Namen automatisch ein."
msgid "Merge satellites by positions"
msgstr "Satelliten nach Pos. zusammenführen"
msgid "Save satellite selection"
msgstr "Satellitenauswahl speichern"
msgid "Extract direct links"
msgstr "Direktlinks extrahieren"
msgid "URL prefix:"
msgstr "URL-Präfix:"
msgid "Invalid prefix for the given URL!"
msgstr "Ungültiges Präfix für die angegebene URL!"
msgid "Alternate window title"
msgstr "Alternativer Fenstertitel"
msgid "Selected type:"
msgstr "Ausgewählt Typ:"

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2018-2022 Víctor Pont
# Copyright (C) 2018-2023 Frank Neirynck
# This file is distributed under the MIT license.
#
# Frank Neirynck <frank@insink.be>, 2018-2020.
# Frank Neirynck <frank@insink.be>, 2018-2023.
# Víctor Pont <victor@pont.cat>, 2021-2022.
msgid ""
msgstr ""
@@ -1441,3 +1441,57 @@ msgstr "Habilita la carga como un archivo si se selecciona una gran cantidad de
msgid "Clear \"New\" flag"
msgstr "Limpiar \"Nuevo\" flag"
msgid "Group by"
msgstr "Agrupar por"
msgid "Replace existing"
msgstr "Sustituir los existentes"
msgid "Already exists"
msgstr "Ya existe"
msgid "Enable unlimited copy buffer"
msgstr "Activar búfer de copia ilimitado"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Activa el búfer de copia ilimitado para la pestaña de bouquets."
msgid "Start time"
msgstr "Hora de inicio"
msgid "End time"
msgstr "Hora final"
msgid "Enable extensions support"
msgstr "Activar el soporte de extensiones"
msgid "After uploading the changes you may need to completely reboot the receiver!"
msgstr "Después de cargar los cambios, es posible que tenga que reiniciar completamente el receptor."
msgid "Remove duplicates"
msgstr "Eliminar duplicados"
msgid "Removed"
msgstr "Eliminado"
msgid "Enables overwriting existing main list services."
msgstr "Permite sobrescribir los servicios existentes de la lista principal."
msgid "Enables skipping services import from lamedb."
msgstr "Permite omitir la importación de servicios desde lamedb."
msgid "Bouquets data only"
msgstr "Sólo datos de bouquets"
msgid "Create Category bouquets"
msgstr "Crear categoría de bouquets"
msgid "Create Regional bouquets"
msgstr "Crear bouquets regionales"
msgid "Skip C-band"
msgstr "Omitir banda C"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Establece automáticamente el nombre seleccionado en la lista de bouquets."

View File

@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"PO-Revision-Date: 2023-02-12 07:10+0100\n"
"PO-Revision-Date: 2023-04-13 02:15+0200\n"
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
"Language-Team: Italian <>\n"
"Language: it\n"
@@ -14,7 +14,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 22.12.2\n"
"X-Generator: Lokalize 22.12.3\n"
msgid "translator-credits"
msgstr "Massimo Pissarello"
@@ -1230,6 +1230,9 @@ msgstr "Dimensione"
msgid "Date"
msgstr "Data"
msgid "Attr."
msgstr "Attr"
msgid "Toggle display position"
msgstr "Commuta posizione visualizzazione"
@@ -1492,3 +1495,58 @@ msgstr "Ora di fine"
msgid "Enable extensions support"
msgstr "Abilita supporto estensioni"
msgid ""
"After uploading the changes you may need to completely reboot the receiver!"
msgstr ""
"Dopo aver caricato le modifiche potrebbe essere necessario riavviare"
" completamente il ricevitore!"
msgid "Remove duplicates"
msgstr "Rimuovi duplicati"
msgid "Removed"
msgstr "Rimosso"
msgid "Enables overwriting existing main list services."
msgstr "Consente di sovrascrivere i servizi esistenti dell'elenco principale."
msgid "Enables skipping services import from lamedb."
msgstr "Consente di saltare l'importazione dei servizi da lamedb."
msgid "Bouquets data only"
msgstr "Bouquet solo dati"
msgid "Create Category bouquets"
msgstr "Crea bouquet categorie"
msgid "Create Regional bouquets"
msgstr "Crea bouquet regionali"
msgid "Skip C-band"
msgstr "Salta banda C"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Imposta automaticamente il nome selezionato nell'elenco dei bouquet."
msgid "Merge satellites by positions"
msgstr "Unisci i satelliti per posizione"
msgid "Save satellite selection"
msgstr "Salva la selezione dei satelliti"
msgid "Extract direct links"
msgstr "Estrai link diretti"
msgid "URL prefix:"
msgstr "Prefisso URL:"
msgid "Invalid prefix for the given URL!"
msgstr "Prefisso non valido per l'URL specificata!"
msgid "Alternate window title"
msgstr "Titolo alternativo della finestra"
msgid "Selected type:"
msgstr "Tipo selezionato:"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 Frank Neirynck
# Copyright (C) 2018-2023 Frank Neirynck
# This file is distributed under the MIT license.
#
# Frank Neirynck <frank@insink.be>, 2018-2022.
@@ -1401,3 +1401,57 @@ msgstr "Maakt uploaden als archief mogelijk als een groot aantal pictogrammen (>
msgid "Clear \"New\" flag"
msgstr "Opruimen \"Niew\" flag"
msgid "Group by"
msgstr "Groeperen per"
msgid "Replace existing"
msgstr "Vervang bestaande"
msgid "Already exists"
msgstr "Bestaat reeds"
msgid "Enable unlimited copy buffer"
msgstr "Onbeperkte kopieerbuffer inschakelen"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Schakelt een onbeperkte kopieerbuffer in voor het boeketten tabblad."
msgid "Start time"
msgstr "Starttijd"
msgid "End time"
msgstr "Eindtijd"
msgid "Enable extensions support"
msgstr "Ondersteuning voor extensies inschakelen"
msgid "After uploading the changes you may need to completely reboot the receiver!"
msgstr "Na het uploaden van de wijzigingen kan het nodig zijn de ontvanger volledig opnieuw op te starten!"
msgid "Remove duplicates"
msgstr "Duplicaten verwijderen"
msgid "Removed"
msgstr "Verwijderd"
msgid "Enables overwriting existing main list services."
msgstr "Maakt het overschrijven van bestaande hoofdlijst diensten mogelijk."
msgid "Enables skipping services import from lamedb."
msgstr "Maakt het overslaan van dienstenimport uit lamedb mogelijk."
msgid "Bouquets data only"
msgstr "Alleen gegevens over boeketten"
msgid "Create Category bouquets"
msgstr "Maak categorie boeketten"
msgid "Create Regional bouquets"
msgstr "Maak regionale boeketten"
msgid "Skip C-band"
msgstr "C-band overslaan"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Stel automatisch de naam in die is geselecteerd in de boeketlijst."

View File

@@ -1443,3 +1443,45 @@ msgstr "Удалить дубликаты"
msgid "Removed"
msgstr "Удалено"
msgid "Enables overwriting existing main list services."
msgstr "Включает перезапись существующих сервисов основного списка."
msgid "Enables skipping services import from lamedb."
msgstr "Включает пропуск импорта сервисов из lamedb."
msgid "Bouquets data only"
msgstr "Только данные букетов"
msgid "Create Category bouquets"
msgstr "Создать букеты по категориям"
msgid "Create Regional bouquets"
msgstr "Создать букеты по регионам"
msgid "Skip C-band"
msgstr "Пропустить C-диапазон"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Автоматическая установка имени, выбранного в списке букетов."
msgid "Merge satellites by positions"
msgstr "Объединять спутники по позициям"
msgid "Save satellite selection"
msgstr "Сохранять выбор спутников"
msgid "Extract direct links"
msgstr "Извлечь прямые ссылки"
msgid "URL prefix:"
msgstr "Префикс URL:"
msgid "Invalid prefix for the given URL!"
msgstr "Недопустимый префикс для данного URL!"
msgid "Alternate window title"
msgstr "Альтернативный заголовок окна"
msgid "Selected type:"
msgstr "Выбран тип:"

View File

@@ -3,9 +3,9 @@ msgstr ""
"Project-Id-Version: DemonEditor\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
"PO-Revision-Date: 2023-02-23 14:22+0300\n"
"PO-Revision-Date: 2023-04-22 16:34+0300\n"
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
"Language-Team: \n"
"Language-Team: audi06_19 <info@dreamosat-forum.com>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -1477,3 +1477,45 @@ msgstr "Kopyaları kaldır"
msgid "Removed"
msgstr "Kaldırıldı"
msgid "Enables overwriting existing main list services."
msgstr "Mevcut ana liste hizmetlerinin üzerine yazılmasını sağlar."
msgid "Enables skipping services import from lamedb."
msgstr "Lamedb'den içe aktarılan hizmetlerin atlanmasını sağlar."
msgid "Bouquets data only"
msgstr "Yalnızca buket verileri"
msgid "Create Category bouquets"
msgstr "Kategori buketleri oluştur"
msgid "Create Regional bouquets"
msgstr "Bölgesel buketler oluşturun"
msgid "Skip C-band"
msgstr "C-band atla"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Buket listesinde seçilen isimleri otomatik olarak ayarlayın."
msgid "Merge satellites by positions"
msgstr "Uyduları konumlara göre birleştir"
msgid "Save satellite selection"
msgstr "Uydu seçimini kaydet"
msgid "Extract direct links"
msgstr "Doğrudan bağlantıları ayıklayın"
msgid "URL prefix:"
msgstr "URL öneki:"
msgid "Invalid prefix for the given URL!"
msgstr "Belirtilen URL için geçersiz önek!"
msgid "Alternate window title"
msgstr "Alternatif pencere başlığı"
msgid "Selected type:"
msgstr "Seçilen tip:"