Compare commits

...

46 Commits

Author SHA1 Message Date
DYefremov
421d9b1c96 control file correction 2022-03-20 11:57:26 +03:00
DYefremov
7357939241 bump version 2022-03-20 11:36:58 +03:00
DYefremov
08ef7bc451 ftp bookmark activation via single click 2022-03-20 11:31:21 +03:00
DYefremov
8b255ec824 Russian, Belarusian and German translations update 2022-03-20 11:26:12 +03:00
DYefremov
c81084015d correction of iptv streams check (#69) 2022-03-20 01:02:19 +03:00
mapi68
66c8e9e916 Update control (#80)
Added homepage and extended info.
2022-03-20 00:57:51 +03:00
DYefremov
4b93ae6950 updated it *.mo file 2022-03-19 21:48:42 +03:00
mapi68
e2cafef113 Italian translation correction (#79)
Fixed column labels
2022-03-19 21:44:16 +03:00
DYefremov
c35be2aa24 added newline parameter 2022-03-19 21:07:49 +03:00
mapi68
852404bae6 Update control (#78)
Added p7zip-full to Depends (to extract Picons from picon,cz)
2022-03-19 13:44:22 +03:00
mapi68
f6d2765137 Italian translation correction (#77) 2022-03-19 13:43:55 +03:00
DYefremov
182c7a9cc7 updated it *.mo file 2022-03-14 20:52:48 +03:00
mapi68
271ea97040 Italian translation update (#73)
Fixed and updated italian language.
2022-03-14 20:34:36 +03:00
DYefremov
364fb68743 minor correction 2022-03-14 14:18:05 +03:00
DYefremov
85a9d5e67e added comment in italian to *.desktop file 2022-03-14 10:10:47 +03:00
mapi68
50c0a0cf37 Update DemonEditor.desktop (#72) 2022-03-14 10:06:29 +03:00
DYefremov
25fd6df967 playback support from the main list 2022-03-13 21:08:33 +03:00
DYefremov
6106e86d18 minor settings dialog improvements 2022-03-13 17:57:06 +03:00
DYefremov
1c5f7fab11 tooltips support for main iptv list 2022-03-13 00:07:59 +03:00
DYefremov
024f90d23f t2mi plp id support for satellite editing tool (#70) 2022-03-11 11:40:23 +03:00
DYefremov
ee3041174c streams playback from the main list 2022-03-10 23:06:06 +03:00
DYefremov
6f28aae40c style correction 2022-03-06 12:22:39 +03:00
DYefremov
1b2de795a2 minor rework of settings dialog 2022-03-06 12:19:54 +03:00
DYefremov
8d485a9993 minor style corrections 2022-02-27 09:03:39 +03:00
Víctor Pont
1721567731 Spanish translation fixes and new strings (#68) 2022-02-24 13:46:21 +03:00
DYefremov
fd4325961c minor style changes 2022-02-23 14:22:29 +03:00
DYefremov
294d32c705 Russian, Belarusian and German translations update 2022-02-22 19:55:39 +03:00
DYefremov
aa961030ce changing path by link for FTP client 2022-02-22 14:44:55 +03:00
DYefremov
bc4c6746c9 minor corrections 2022-02-22 14:18:40 +03:00
DYefremov
61282b0cc8 copied tr *.mo file 2022-02-22 10:21:10 +03:00
audi06_19
07e855f99d Turkish translation update (#66) 2022-02-22 10:15:12 +03:00
DYefremov
0d0f19122b updated comments in *.desktop file 2022-02-21 20:33:24 +03:00
DYefremov
4789688efd updated es *.mo file 2022-02-21 20:09:40 +03:00
Víctor Pont
b13c2c3be0 Spanish translation update (#65) 2022-02-21 20:03:21 +03:00
DYefremov
d72189abc4 bump version 2022-02-21 15:03:17 +03:00
DYefremov
35d194100b added keyboard shortcut for renaming 2022-02-21 14:28:47 +03:00
DYefremov
aa0b97b9ae added extra tab for IPTV 2022-02-21 12:22:44 +03:00
DYefremov
5f54452ee2 fixed getting satellites list from FlySat 2022-02-21 00:23:07 +03:00
DYefremov
580e8ca82c basic bookmarks support for the FTP client 2022-02-13 00:29:44 +03:00
DYefremov
4ba2fb1a04 skip importing groups from m3u for Neutrino 2022-02-12 14:22:48 +03:00
DYefremov
b4612c26cb changed file names reading for FTP client 2022-02-09 21:56:45 +03:00
DYefremov
9a8b1e871d bump version 2022-02-05 15:20:28 +03:00
DYefremov
3a53a95f86 fixed current channel recording on Windows 2022-02-05 15:11:29 +03:00
DYefremov
24a94cfe9a improved MPV support 2022-02-05 13:50:59 +03:00
DYefremov
0ca08e3a1d fixed bouquets DND for macOS (#64) 2022-02-04 02:12:48 +03:00
DYefremov
db4e9d2696 fixed single bouquets import on macOS (#63) 2022-02-04 01:16:37 +03:00
53 changed files with 4017 additions and 2331 deletions

View File

@@ -5,7 +5,9 @@ Comment=Channel and satellite list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
Comment[it]=Editor di liste canali e satelliti per Enigma2
Comment[tr]=Enigma2 için kanal ve uydu listesi editörü
Comment[es]=Editor de listas de canales y satélites para Enigma2
Icon=demon-editor
Exec=bash -c 'cd $(dirname %k) && ./start.py'
Terminal=false

View File

@@ -135,8 +135,8 @@ class UtfFTP(FTP):
files = []
self.dir(path, files.append)
for f in files:
f_data = f.split()
f_path = os.path.join(path, " ".join(f_data[8:]))
f_data = self.get_file_data(f)
f_path = f_data[8]
if f_data[0][0] == "d":
try:
@@ -310,9 +310,8 @@ class UtfFTP(FTP):
files = []
self.dir(path, files.append)
for f in files:
f_data = f.split()
name = " ".join(f_data[8:])
f_path = path + "/" + name
f_data = self.get_file_data(f)
f_path = f"{path}/{f_data[8]}"
if f_data[0][0] == "d":
self.delete_dir(f_path, callback)
@@ -351,6 +350,15 @@ class UtfFTP(FTP):
return resp
@staticmethod
def get_file_data(file):
""" Returns a prepared list of file data from a file string. """
f_data = file.split()
# Ignoring space in file name.
f_data = f_data[0:9]
f_data[8] = file[file.index(f_data[8]):]
return f_data
def download_data(*, settings, download_type=DownloadType.ALL, callback=log, files_filter=None):
with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -61,8 +61,8 @@ BouquetService = namedtuple("BouquetService", ["name", "type", "data", "num"])
Satellite = namedtuple("Satellite", ["name", "flags", "position", "transponders"])
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner",
"system", "modulation", "pls_mode", "pls_code", "is_id"])
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner", "system",
"modulation", "pls_mode", "pls_code", "is_id", "t2mi_plp_id"])
class TrType(Enum):
@@ -247,6 +247,7 @@ def is_transponder_valid(tr: Transponder):
tr.pls_mode is None or int(tr.pls_mode)
tr.pls_code is None or int(tr.pls_code)
tr.is_id is None or int(tr.is_id)
tr.t2mi_plp_id is None or int(tr.t2mi_plp_id)
except (TypeError, ValueError) as e:
log(f"Transponder validation error: {e}\n{tr}")
return False

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -98,7 +98,7 @@ class BouquetsWriter:
self.write_bouquet(f"{self._path}userbouquet.{bq_name}.{bqs.type}", bq.name, bq.services)
line.append(self._SERVICE.format(2 if bqs.type == BqType.RADIO.value else 1, bq_name, bqs.type))
with open(f"{self._path}bouquets.{bqs.type}", "w", encoding="utf-8") as file:
with open(f"{self._path}bouquets.{bqs.type}", "w", encoding="utf-8", newline="\n") as file:
file.writelines(line)
def write_bouquet(self, path, name, services):
@@ -136,7 +136,7 @@ class BouquetsWriter:
else:
bouquet.append(f"#SERVICE {data}\n")
with open(path, "w", encoding="utf-8") as file:
with open(path, "w", encoding="utf-8", newline="\n") as file:
file.writelines(bouquet)
def write_sub_bouquet(self, path, file_name, bq, bq_type):
@@ -148,7 +148,7 @@ class BouquetsWriter:
self.write_bouquet(f"{path}{bq_name}", sb.name, sb.services)
bouquet.append(f"#SERVICE 1:7:{sb_type}:0:0:0:0:0:0:0:FROM BOUQUET \"{bq_name}\" ORDER BY bouquet\n")
with open(f"{self._path}userbouquet.{file_name}.{bq_type}", "w", encoding="utf-8") as file:
with open(f"{self._path}userbouquet.{file_name}.{bq_type}", "w", encoding="utf-8", newline="\n") as file:
file.writelines(bouquet)

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -310,7 +310,7 @@ class LameDbWriter:
def write(self):
if self._fmt == 4:
# Writing lamedb file ver.4
with open(self._path + _FILE_NAME, "w", encoding="utf-8") as file:
with open(self._path + _FILE_NAME, "w", encoding="utf-8", newline="\n") as file:
file.writelines(LameDbReader.get_services_lines(self._services))
elif self._fmt == 5:
self.write_to_lamedb5()
@@ -335,7 +335,7 @@ class LameDbWriter:
lines.extend(services_lines)
lines.append(_END_LINE)
with open(self._path + "lamedb5", "w", encoding="utf-8") as file:
with open(self._path + "lamedb5", "w", encoding="utf-8", newline="\n") as file:
file.writelines(lines)

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -40,6 +40,7 @@ from app.ui.uicommons import IPTV_ICON
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
PICON_FORMAT = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"
class StreamType(Enum):
@@ -49,6 +50,11 @@ class StreamType(Enum):
NONE_REC_2 = "5002"
E_SERVICE_URI = "8193"
E_SERVICE_HLS = "8739"
UNKNOWN = "0"
@classmethod
def _missing_(cls, value):
return cls.UNKNOWN
def parse_m3u(path, s_type, detect_encoding=True, params=None):
@@ -76,6 +82,7 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
p_id = "1_0_1_0_0_0_0_0_0_0.png"
st = BqServiceType.IPTV.name
params = params or [0, 0, 0, 0]
m_name = BqServiceType.MARKER.name
for line in str(data, encoding=encoding, errors="ignore").splitlines():
if line.startswith("#EXTINF"):
@@ -88,26 +95,30 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
d = {data[i].lower().strip(" ="): data[i + 1] for i in range(0, len(data) - 1, 2)}
picon = d.get("tvg-logo", None)
grp_name = d.get("group-title", None)
if grp_name not in groups:
groups.add(grp_name)
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
marker_counter += 1
mr = Service(None, None, None, grp_name, *aggr[0:3], BqServiceType.MARKER.name, *aggr, fav_id, None)
services.append(mr)
if s_type is SettingsType.ENIGMA_2:
grp_name = d.get("group-title", None)
if grp_name not in groups:
groups.add(grp_name)
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
marker_counter += 1
mr = Service(None, None, None, grp_name, *aggr[0:3], m_name, *aggr, fav_id, None)
services.append(mr)
elif line.startswith("#EXTGRP") and s_type is SettingsType.ENIGMA_2:
grp_name = line.strip("#EXTGRP:").strip()
if grp_name not in groups:
groups.add(grp_name)
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
marker_counter += 1
mr = Service(None, None, None, grp_name, *aggr[0:3], BqServiceType.MARKER.name, *aggr, fav_id, None)
mr = Service(None, None, None, grp_name, *aggr[0:3], m_name, *aggr, fav_id, None)
services.append(mr)
elif not line.startswith("#"):
url = line.strip()
params[0] = sid_counter
sid_counter += 1
fav_id = get_fav_id(url, name, s_type, params)
if s_type is SettingsType.ENIGMA_2:
p_id = get_picon_id(params)
if all((name, url, fav_id)):
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], st, picon, p_id, *s_aggr, url, fav_id, None)
services.append(srv)
@@ -152,5 +163,11 @@ def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)
def get_picon_id(params=None, st_type=None, s_id=0, srv_type=1):
st_type = st_type or StreamType.NONE_TS.value
params = params or (0, 0, 0, 0)
return PICON_FORMAT.format(st_type, s_id, srv_type, *params)
if __name__ == "__main__":
pass

View File

@@ -1,4 +1,32 @@
""" Module foe parsing Satellites.xml
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Author: Dmitriy Yefremov
#
""" Module for parsing satellites.xml file.
For more info see __COMMENT
"""
@@ -62,6 +90,8 @@ def write_satellites(satellites, data_path):
transponder_child.setAttribute("pls_code", tr.pls_code)
if tr.is_id:
transponder_child.setAttribute("is_id", tr.is_id)
if tr.t2mi_plp_id:
transponder_child.setAttribute("t2mi_plp_id", tr.t2mi_plp_id)
sat_child.appendChild(transponder_child)
root.appendChild(sat_child)
doc.writexml(open(data_path, "w"),
@@ -87,9 +117,10 @@ def parse_transponders(elem, sat_name):
MODULATION[atr["modulation"].value],
atr["pls_mode"].value if "pls_mode" in atr else None,
atr["pls_code"].value if "pls_code" in atr else None,
atr["is_id"].value if "is_id" in atr else None)
atr["is_id"].value if "is_id" in atr else None,
atr["t2mi_plp_id"].value if "t2mi_plp_id" in atr else None)
except Exception as e:
message = "Error: can't parse transponder for '{}' satellite! {}".format(sat_name, repr(e))
message = f"Error: can't parse transponder for '{sat_name}' satellite! {repr(e)}"
log(message)
else:
transponders.append(tr)
@@ -97,7 +128,7 @@ def parse_transponders(elem, sat_name):
def parse_sat(elem):
""" Parsing satellite """
""" Parsing satellite. """
sat_name = elem.attributes["name"].value
return Satellite(sat_name,
elem.attributes["flags"].value,
@@ -106,7 +137,7 @@ def parse_sat(elem):
def parse_satellites(path):
""" Parsing satellites from xml"""
""" Parsing satellites from xml. """
dom = parse(path)
satellites = []

View File

@@ -92,6 +92,7 @@ class Defaults(Enum):
FAV_CLICK_MODE = 0
PLAY_STREAMS_MODE = 1 if IS_DARWIN else 0
STREAM_LIB = "mpv" if IS_WIN else "vlc"
MAIN_LIST_PLAYBACK = False
PROFILE_FOLDER_DEFAULT = False
RECORDS_PATH = DATA_PATH + "records{}".format(SEP)
ACTIVATE_TRANSCODING = False
@@ -416,7 +417,7 @@ class Settings:
@property
def profile_data_path(self):
return "{}data{}{}{}".format(self.default_data_path, SEP, self._current_profile, SEP)
return f"{self.default_data_path}data{SEP}{self._current_profile}{SEP}"
@profile_data_path.setter
def profile_data_path(self, value):
@@ -425,8 +426,8 @@ class Settings:
@property
def profile_picons_path(self):
if self.profile_folder_is_default:
return "{}picons{}".format(self.profile_data_path, SEP)
return "{}{}{}".format(self.default_picon_path, self._current_profile, SEP)
return f"{self.profile_data_path}picons{SEP}"
return f"{self.default_picon_path}{self._current_profile}{SEP}"
@profile_picons_path.setter
def profile_picons_path(self, value):
@@ -435,8 +436,8 @@ class Settings:
@property
def profile_backup_path(self):
if self.profile_folder_is_default:
return "{}backup{}".format(self.profile_data_path, SEP)
return "{}{}{}".format(self.default_backup_path, self._current_profile, SEP)
return f"{self.profile_data_path}backup{SEP}"
return f"{self.default_backup_path}{self._current_profile}{SEP}"
@profile_backup_path.setter
def profile_backup_path(self, value):
@@ -492,6 +493,22 @@ class Settings:
def stream_lib(self, value):
self._settings["stream_lib"] = value
@property
def fav_click_mode(self):
return self._settings.get("fav_click_mode", Defaults.FAV_CLICK_MODE.value)
@fav_click_mode.setter
def fav_click_mode(self, value):
self._settings["fav_click_mode"] = value
@property
def main_list_playback(self):
return self._settings.get("main_list_playback", Defaults.MAIN_LIST_PLAYBACK.value)
@main_list_playback.setter
def main_list_playback(self, value):
self._settings["main_list_playback"] = value
# *********** EPG ************ #
@property
@@ -503,6 +520,16 @@ class Settings:
def epg_options(self, value):
self._cp_settings["epg_options"] = value
# *********** FTP ************ #
@property
def ftp_bookmarks(self):
return self._cp_settings.get("ftp_bookmarks", [])
@ftp_bookmarks.setter
def ftp_bookmarks(self, value):
self._cp_settings["ftp_bookmarks"] = value
# ***** Program settings ***** #
@property
@@ -569,14 +596,6 @@ class Settings:
def enable_send_to(self, value):
self._settings["enable_send_to"] = value
@property
def fav_click_mode(self):
return self._settings.get("fav_click_mode", Defaults.FAV_CLICK_MODE.value)
@fav_click_mode.setter
def fav_click_mode(self, value):
self._settings["fav_click_mode"] = value
@property
def language(self):
return self._settings.get("language", locale.getlocale()[0] or "en_US")
@@ -785,7 +804,10 @@ class Settings:
Settings.write_settings(Settings.get_default_settings())
with open(CONFIG_FILE, "r", encoding="utf-8") as config_file:
return json.load(config_file)
try:
return json.load(config_file)
except ValueError as e:
raise SettingsReadException(e)
@staticmethod
def get_default_settings(profile_name="default"):

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -33,6 +33,7 @@ from datetime import datetime
from gi.repository import Gdk, Gtk, GObject
from app.commons import run_task, log, _DATE_FORMAT, run_with_delay
from app.settings import IS_DARWIN, IS_LINUX, IS_WIN
class Player(Gtk.DrawingArea):
@@ -115,22 +116,21 @@ class Player(Gtk.DrawingArea):
Based on gtkvlc.py[get_window_pointer] example from here:
https://github.com/oaubert/python-vlc/tree/master/examples
"""
if sys.platform == "linux":
if IS_LINUX:
return self.get_window().get_xid()
else:
is_darwin = sys.platform == "darwin"
try:
import ctypes
libgdk = ctypes.CDLL("libgdk-3.0.dylib" if is_darwin else "libgdk-3-0.dll")
libgdk = ctypes.CDLL("libgdk-3.0.dylib" if IS_DARWIN else "libgdk-3-0.dll")
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
log(f"{__class__.__name__}: Load library error: {e}")
else:
# https://gitlab.gnome.org/GNOME/pygobject/-/issues/112
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
gpointer = ctypes.pythonapi.PyCapsule_GetPointer(self.get_window().__gpointer__, None)
get_pointer = libgdk.gdk_quartz_window_get_nsview if is_darwin else libgdk.gdk_win32_window_get_handle
get_pointer = libgdk.gdk_quartz_window_get_nsview if IS_DARWIN else libgdk.gdk_win32_window_get_handle
get_pointer.restype = ctypes.c_void_p
get_pointer.argtypes = [ctypes.c_void_p]
@@ -171,7 +171,7 @@ class Player(Gtk.DrawingArea):
elif name == "vlc":
return VlcPlayer.get_instance(mode, widget)
else:
raise NameError("There is no such [{}] implementation.".format(name))
raise NameError(f"There is no such [{name}] implementation.")
class MpvPlayer(Player):
@@ -191,7 +191,7 @@ class MpvPlayer(Player):
input_cursor=False,
cursor_autohide="no")
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
log(f"{__class__.__name__}: Load library error: {e}")
raise ImportError("No libmpv is found. Check that it is installed!")
else:
self._mode = mode
@@ -202,11 +202,22 @@ class MpvPlayer(Player):
log("Starting playback...")
self.emit("played", 0)
t_list = self._player._get_property("track-list")
if t_list:
# Audio tracks.
a_tracks = filter(lambda t: t.get("type", "") == "audio", t_list)
self.emit("audio-track", ((t.get("id", 1), t.get("lang", "Unknown")) for t in a_tracks))
# Subtitle.
sub_tracks = [(0, "no")]
tracks = filter(lambda t: t.get("type", "") == "sub", t_list)
[sub_tracks.append((t.get("id", 1), t.get("lang", "Unknown"))) for t in tracks]
self.emit("subtitle-track", sub_tracks)
@self._player.event_callback(mpv.MpvEventID.END_FILE)
def on_end(event):
event = event.get("event", {})
if event.get("reason", mpv.MpvEventEndFile.ERROR) == mpv.MpvEventEndFile.ERROR:
log("Stream playback error: {}".format(event.get("error", mpv.ErrorCode.GENERIC)))
log(f"Stream playback error: {event.get('error', mpv.ErrorCode.GENERIC)}")
self.emit("error", "Can't Playback!")
@classmethod
@@ -243,6 +254,15 @@ class MpvPlayer(Player):
def is_playing(self):
return self._is_playing
def set_audio_track(self, track):
self._player._set_property("aid", track)
def set_subtitle_track(self, track):
self._player._set_property("sub", track)
def set_aspect_ratio(self, ratio):
self._player._set_property("aspect", ratio or "-1.0")
class GstPlayer(Player):
""" Simple wrapper for GStreamer playbin. """
@@ -260,7 +280,7 @@ class GstPlayer(Player):
# Initialization of GStreamer.
Gst.init(sys.argv)
except (OSError, ValueError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
log(f"{__class__.__name__}: Load library error: {e}")
raise ImportError("No GStreamer is found. Check that it is installed!")
else:
self.STATE = Gst.State
@@ -293,11 +313,11 @@ class GstPlayer(Player):
self._player.set_property("uri", mrl)
log("Setting the URL for playback: {}".format(mrl))
log(f"Setting the URL for playback: {mrl}")
ret = self._player.set_state(self.STATE.PLAYING)
if ret == self.STAT_RETURN.FAILURE:
msg = "ERROR: Unable to set the 'PLAYING' state for '{}'.".format(mrl)
msg = f"ERROR: Unable to set the 'PLAYING' state for '{mrl}'."
log(msg)
self.emit("error", msg)
else:
@@ -356,7 +376,7 @@ class GstPlayer(Player):
tags = self._player.emit("get-video-tags", i)
if tags:
_, cod = tags.get_string("video-codec")
log("Video codec: {}".format(cod or "unknown"))
log(f"Video codec: {cod or 'unknown'}")
nr_audio = self._player.get_property("n-audio")
for i in range(nr_audio):
@@ -364,7 +384,7 @@ class GstPlayer(Player):
tags = self._player.emit("get-audio-tags", i)
if tags:
_, cod = tags.get_string("audio-codec")
log("Audio codec: {}".format(cod or "unknown"))
log(f"Audio codec: {cod or 'unknown'}")
class VlcPlayer(Player):
@@ -378,18 +398,18 @@ class VlcPlayer(Player):
def __init__(self, mode, widget):
super().__init__(mode, widget)
try:
if sys.platform == "win32":
if IS_WIN:
os.add_dll_directory(r"C:\Program Files\VideoLAN\VLC")
from app.tools import vlc
from app.tools.vlc import EventType
args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
args = f"--quiet {'' if IS_DARWIN else '--no-xlib'}"
self._player = vlc.Instance(args).media_player_new()
vlc.libvlc_video_set_key_input(self._player, False)
vlc.libvlc_video_set_mouse_input(self._player, False)
except (OSError, AttributeError, NameError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
log(f"{__class__.__name__}: Load library error: {e}")
raise ImportError("No VLC is found. Check that it is installed!")
else:
self._mode = mode
@@ -457,17 +477,17 @@ class VlcPlayer(Player):
def on_playback_start(self, event):
self.emit("played", self._player.get_media().get_duration())
# Audio tracks
# Audio tracks.
a_desc = self._player.audio_get_track_description()
self.emit("audio-track", [(t[0], t[1].decode(encoding="utf-8", errors="ignore")) for t in a_desc])
# Subtitle
# Subtitle.
s_desc = self._player.video_get_spu_description()
self.emit("subtitle-track", [(s[0], s[1].decode(encoding="utf-8", errors="ignore")) for s in s_desc])
def init_video_widget(self, widget):
if sys.platform == "linux":
if IS_LINUX:
self._player.set_xwindow(self.get_window_handle())
elif sys.platform == "darwin":
elif IS_DARWIN:
self._player.set_nsobject(self.get_window_handle())
else:
self._player.set_hwnd(self.get_window_handle())
@@ -481,15 +501,18 @@ class Recorder:
def __init__(self, settings):
try:
if IS_WIN:
os.add_dll_directory(r"C:\Program Files\VideoLAN\VLC")
from app.tools import vlc
from app.tools.vlc import EventType
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
log(f"{__class__.__name__}: Load library error: {e}")
raise ImportError
else:
self._settings = settings
self._is_record = False
args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
args = f"--quiet {'' if IS_DARWIN else '--no-xlib'}"
self._recorder = vlc.Instance(args).media_player_new()
@classmethod
@@ -506,7 +529,8 @@ class Recorder:
path = self._settings.records_path
os.makedirs(os.path.dirname(path), exist_ok=True)
d_now = datetime.now().strftime(_DATE_FORMAT)
path = "{}{}_{}".format(path, name.replace(" ", "_"), d_now.replace(" ", "_"))
d_now = d_now.replace(" ", "_").replace(":", "-") if IS_WIN else d_now.replace(" ", "_")
path = f"{path}{name.replace(' ', '_')}_{d_now}"
cmd = self.get_transcoding_cmd(path) if self._settings.activate_transcoding else self._CMD.format(path)
media = self._recorder.get_instance().media_new(url, cmd)
media.get_mrl()
@@ -514,7 +538,7 @@ class Recorder:
self._recorder.set_media(media)
self._is_record = True
self._recorder.play()
log("Record started {}".format(d_now))
log(f"Record started {d_now}")
@run_task
def stop(self):
@@ -536,7 +560,7 @@ class Recorder:
def get_transcoding_cmd(self, path):
presets = self._settings.transcoding_presets
prs = presets.get(self._settings.active_preset)
return self._TR_CMD.format(",".join("{}={}".format(k, v) for k, v in prs.items()), path)
return self._TR_CMD.format(",".join(f"{k}={v}" for k, v in prs.items()), path)
if __name__ == "__main__":

View File

@@ -57,7 +57,7 @@ class PiconsCzDownloader:
_PERM_URL = "https://picon.cz/download/7337"
_BASE_URL = "https://picon.cz/download/"
_BASE_LOGO_URL = "https://picon.cz/picon/0/"
_HEADER = {"User-Agent": "DemonEditor/2.1.1", "Referer": ""}
_HEADER = {"User-Agent": "DemonEditor/2.2.1", "Referer": ""}
_LINK_PATTERN = re.compile(r"((.*)-\d+x\d+)-(.*)_by_chocholousek.7z$")
_FILE_PATTERN = re.compile(b"\\s+(1_.*\\.png).*")

View File

@@ -195,36 +195,37 @@ class SatellitesParser(HTMLParser):
names = []
pos = ""
pos_url = ""
satellites = []
def normalize_pos(p):
return f"{float(p[:-1])}{p[-1]}" if "." not in p else p
def get_sat(r):
nonlocal pos
nonlocal pos_url
# Uniting satellites in position.
if re.match(pos_pat, r[2]):
pos_url = r[2]
name = r[1]
pos = normalize_pos(self.parse_position(r[3]))
for row in filter(lambda x: len(x) > 6, self._rows):
if re.match(sat_pat, row[1]):
row.pop(0)
names.append(name)
return name, pos, r[5], r[0], False
r_size = len(r)
if r_size == 5:
name = r[1]
names.append(name)
return name, pos, r[3], r[0], False
if r_size == 6:
if names:
name = "/".join(names)
if re.match(sat_pat, row[0]) and row[-2]: # r[-2] -> skip EMPTY satellites!
if re.match(pos_pat, row[0]):
names.clear()
return name, pos, None, pos_url, False
pos_url = row[0]
name = row[3]
pos = normalize_pos(self.parse_position(row[-4]))
names.append(name)
satellites.append((name, pos, row[-2], row[2], False))
return r[1], normalize_pos(self.parse_position(r[2])), r[4], r[0], False
if len(row) == 7:
single_pos = normalize_pos(self.parse_position(row[-4]))
name = row[1]
if pos == single_pos:
names.append(name)
else:
# Uniting satellites in position.
if len(names) > 1:
satellites.append(("/".join(names), pos, None, pos_url, False))
names.clear()
satellites.append((name, single_pos, row[-2], row[0], False))
return list(filter(None, map(get_sat, filter(lambda row: row and re.match(sat_pat, row[0]), self._rows))))
return satellites
def get_satellites_for_lyng_sat(self):
base_url = "https://www.lyngsat.com/"
@@ -326,7 +327,7 @@ class SatellitesParser(HTMLParser):
if is_transponder_valid(tr):
n_trs.append(tr)
tr = Transponder(f"{freq}000", f"{sr}000", pol, fec, sys, mod, pls_mode, pls_code, None)
tr = Transponder(f"{freq}000", f"{sr}000", pol, fec, sys, mod, pls_mode, pls_code, None, None)
if is_transponder_valid(tr):
trs.append(tr)
@@ -362,7 +363,7 @@ class SatellitesParser(HTMLParser):
if plp is not None:
log(f"Detected T2-MI transponder! [{freq} {sr} {pol}] ")
tr = Transponder(f"{freq}000", f"{sr}000", pol, fec, sys, mod, pls_mode, pls_code, is_id)
tr = Transponder(f"{freq}000", f"{sr}000", pol, fec, sys, mod, pls_mode, pls_code, is_id, None)
if is_transponder_valid(tr):
trs.append(tr)
@@ -399,7 +400,7 @@ class SatellitesParser(HTMLParser):
if t2_mi:
log(f"Detected T2-MI transponder! [{freq} {sr} {pol}] ")
tr = Transponder(freq, f"{sr}000", pol, fec, sys, mod, pls_id, pls_code, is_id)
tr = Transponder(freq, f"{sr}000", pol, fec, sys, mod, pls_id, pls_code, is_id, None)
if is_transponder_valid(tr):
trs.append(tr)

View File

@@ -617,10 +617,10 @@ class TimerTool(Gtk.Box):
return
fav_id = None
if source == self._app.FAV_MODEL_NAME:
if source == self._app.FAV_MODEL:
model = self._app.fav_view.get_model()
fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.FAV_ID)
elif source == self._app.SERVICE_MODEL_NAME:
elif source == self._app.SERVICE_MODEL:
model = self._app.services_view.get_model()
fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.SRV_FAV_ID)
@@ -727,7 +727,7 @@ class RecordingsTool(Gtk.Box):
model.append((None, self.ROOT, self._ftp.pwd()))
for f in files:
f_data = f.split()
f_data = self._ftp.get_file_data(f)
if len(f_data) < 9:
log(f"{__class__.__name__}. Folder data parsing error. [{f}]")
continue
@@ -735,7 +735,7 @@ class RecordingsTool(Gtk.Box):
f_type = f_data[0][0]
if f_type == "d":
model.append((self._icon, " ".join(f_data[8:]), self._ftp.pwd()))
model.append((self._icon, f_data[8], self._ftp.pwd()))
def on_path_activated(self, view, path, column):
row = view.get_model()[path][:]

View File

@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor. -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkAboutDialog" id="about_dialog">
<property name="can_focus">False</property>
@@ -40,8 +40,8 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">2.1.1 Beta</property>
<property name="copyright">2018-2021 Dmitriy Yefremov
<property name="version">2.2.1 Beta</property>
<property name="copyright">2018-2022 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
<property name="website">https://dyefremov.github.io/DemonEditor/</property>

View File

@@ -34,11 +34,23 @@ Author: Dmitriy Yefremov
<!-- interface-description Enigma2 channel and satellite list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkMenu" id="bookmark_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="bookmark_remove_menu_item">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_bookmark_remove" swapped="no"/>
</object>
</child>
</object>
<object class="GtkListStore" id="bookmarks_list_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name url -->
<!-- column-name path -->
<column type="gchararray"/>
</columns>
</object>
@@ -174,9 +186,66 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="bookmark_button_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="homogeneous">True</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkToggleButton" id="bookmarks_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Bookmarks</property>
<child>
<object class="GtkImage" id="bookmark_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">user-bookmarks-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="add_ftp_bookmark_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Add bookmark</property>
<signal name="clicked" handler="on_bookmark_add" swapped="no"/>
<child>
<object class="GtkImage" id="ftp_remove_button_image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">bookmark-new-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="ftp_actions_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
@@ -184,7 +253,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Add folder</property>
<property name="tooltip_text" translatable="yes">New folder</property>
<signal name="clicked" handler="on_ftp_create_folder" object="ftp_name_column_renderer" swapped="no"/>
<child>
<object class="GtkImage" id="ftp_add_folder_button_image">
@@ -246,20 +315,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="bookmark_button">
<property name="can_focus">False</property>
<property name="model">bookmarks_list_store</property>
<property name="id_column">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">5</property>
<property name="position">3</property>
</packing>
</child>
</object>
@@ -270,122 +326,192 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="ftp_view_scrolled_window">
<object class="GtkBox" id="ftp_data_box">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_height">100</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkTreeView" id="ftp_view">
<object class="GtkBox" id="bookmarks_box">
<property name="width_request">150</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkScrolledWindow" id="bookmarks_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="bookmarks_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">bookmarks_list_store</property>
<property name="headers_visible">False</property>
<property name="search_column">0</property>
<property name="tooltip_column">0</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="bookmark_popup_menu" swapped="no"/>
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
<signal name="row-activated" handler="on_bookmark_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="bookmarks_selection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="bookmark_name_column">
<property name="title" translatable="yes">Name</property>
<child>
<object class="GtkCellRendererText" id="bookmark_name_renderer">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="ftp_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">ftp_list_store</property>
<property name="search_column">1</property>
<property name="rubber_banding">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="ftp_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_ftp_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_ftp_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
<signal name="row-activated" handler="on_ftp_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="ftp_selection">
<property name="mode">multiple</property>
</object>
</child>
<property name="shadow_type">in</property>
<property name="min_content_height">100</property>
<child>
<object class="GtkTreeViewColumn" id="ftp_name_column">
<property name="resizable">True</property>
<property name="min_width">100</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="ftp_icon_column_renderer">
<property name="xalign">0.019999999552965164</property>
<object class="GtkTreeView" id="ftp_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">ftp_list_store</property>
<property name="search_column">1</property>
<property name="rubber_banding">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="ftp_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_ftp_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_ftp_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
<signal name="row-activated" handler="on_ftp_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="ftp_selection">
<property name="mode">multiple</property>
</object>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="ftp_name_column_renderer">
<property name="xalign">0.019999999552965164</property>
<property name="ellipsize">end</property>
<signal name="edited" handler="on_ftp_renamed" swapped="no"/>
<object class="GtkTreeViewColumn" id="ftp_name_column">
<property name="resizable">True</property>
<property name="min_width">100</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="ftp_icon_column_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="ftp_name_column_renderer">
<property name="xalign">0.019999999552965164</property>
<property name="ellipsize">end</property>
<signal name="edited" handler="on_ftp_renamed" swapped="no"/>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_size_column">
<property name="sizing">fixed</property>
<property name="min_width">75</property>
<property name="title" translatable="yes">Size</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">2</property>
<child>
<object class="GtkCellRendererText" id="ftp_size_column_renderer">
<property name="xalign">0.94999998807907104</property>
<object class="GtkTreeViewColumn" id="ftp_size_column">
<property name="sizing">fixed</property>
<property name="min_width">75</property>
<property name="title" translatable="yes">Size</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">2</property>
<child>
<object class="GtkCellRendererText" id="ftp_size_column_renderer">
<property name="xalign">0.94999998807907104</property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_date_column">
<property name="min_width">75</property>
<property name="title" translatable="yes">Date</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererText" id="ftp_date_column_renderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_attr_column">
<property name="sizing">fixed</property>
<property name="min_width">85</property>
<property name="title" translatable="yes">Attr.</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">4</property>
<child>
<object class="GtkCellRendererText" id="ftp_attr_column_renderer">
<property name="xalign">0.50999999046325684</property>
<property name="ellipsize">end</property>
<object class="GtkTreeViewColumn" id="ftp_date_column">
<property name="min_width">75</property>
<property name="title" translatable="yes">Date</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererText" id="ftp_date_column_renderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_extra_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Extra</property>
<child>
<object class="GtkCellRendererText" id="ftp_extra_column_renderer"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
<object class="GtkTreeViewColumn" id="ftp_attr_column">
<property name="sizing">fixed</property>
<property name="min_width">85</property>
<property name="title" translatable="yes">Attr.</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">4</property>
<child>
<object class="GtkCellRendererText" id="ftp_attr_column_renderer">
<property name="xalign">0.50999999046325684</property>
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_extra_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Extra</property>
<child>
<object class="GtkCellRendererText" id="ftp_extra_column_renderer"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
@@ -395,7 +521,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkBox" id="ftp_status_bar_box">
<property name="height_request">24</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -484,7 +610,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Add folder</property>
<property name="tooltip_text" translatable="yes">New folder</property>
<signal name="clicked" handler="on_file_create_folder" object="file_name_column_renderer" swapped="no"/>
<child>
<object class="GtkImage" id="pc_add_folder_button_image">
@@ -649,7 +775,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkBox" id="file_status_bar_box">
<property name="height_request">24</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -747,6 +873,7 @@ Author: Dmitriy Yefremov
<object class="GtkImageMenuItem" id="ftp_create_folder_menu_item">
<property name="label" translatable="yes">Create folder</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="image">ftp_create_folder_image</property>
<property name="use_stock">False</property>
@@ -758,6 +885,7 @@ Author: Dmitriy Yefremov
<object class="GtkImageMenuItem" id="ftp_edit_menu_item">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
@@ -770,6 +898,7 @@ Author: Dmitriy Yefremov
<property name="label" translatable="yes">Rename</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="sensitive">False</property>
<property name="image">rename_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_ftp_rename" object="ftp_name_column_renderer" swapped="no"/>
@@ -787,6 +916,7 @@ Author: Dmitriy Yefremov
<object class="GtkImageMenuItem" id="ftp_remove_menu_item">
<property name="label" translatable="yes">Remove</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="image">remove_image_2</property>
<property name="use_stock">False</property>

View File

@@ -159,6 +159,7 @@ class FtpClientBox(Gtk.HBox):
"on_disconnect": self.on_disconnect,
"on_ftp_row_activated": self.on_ftp_row_activated,
"on_file_row_activated": self.on_file_row_activated,
"on_bookmark_activated": self.on_bookmark_activated,
"on_ftp_edit": self.on_ftp_edit,
"on_ftp_rename": self.on_ftp_rename,
"on_ftp_renamed": self.on_ftp_renamed,
@@ -168,6 +169,7 @@ class FtpClientBox(Gtk.HBox):
"on_file_copy": self.on_file_copy,
"on_file_remove": self.on_file_remove,
"on_ftp_remove": self.on_ftp_file_remove,
"on_bookmark_remove": self.on_bookmark_remove,
"on_file_create_folder": self.on_file_create_folder,
"on_ftp_create_folder": self.on_ftp_create_folder,
"on_view_drag_begin": self.on_view_drag_begin,
@@ -176,6 +178,7 @@ class FtpClientBox(Gtk.HBox):
"on_file_drag_data_get": self.on_file_drag_data_get,
"on_file_drag_data_received": self.on_file_drag_data_received,
"on_view_drag_end": self.on_view_drag_end,
"on_bookmark_add": self.on_bookmark_add,
"on_view_popup_menu": on_popup_menu,
"on_view_key_press": self.on_view_key_press,
"on_view_press": self.on_view_press,
@@ -192,6 +195,8 @@ class FtpClientBox(Gtk.HBox):
self._file_view = builder.get_object("file_view")
self._file_model = builder.get_object("file_list_store")
self._file_name_renderer = builder.get_object("file_name_column_renderer")
self._bookmark_view = builder.get_object("bookmarks_view")
self._bookmark_model = builder.get_object("bookmarks_list_store")
# Buttons
self._connect_button = builder.get_object("connect_button")
disconnect_button = builder.get_object("disconnect_button")
@@ -200,7 +205,10 @@ class FtpClientBox(Gtk.HBox):
disconnect_button.bind_property("visible", builder.get_object("ftp_edit_menu_item"), "sensitive")
disconnect_button.bind_property("visible", builder.get_object("ftp_rename_menu_item"), "sensitive")
disconnect_button.bind_property("visible", builder.get_object("ftp_remove_menu_item"), "sensitive")
disconnect_button.bind_property("visible", builder.get_object("add_ftp_bookmark_button"), "sensitive")
self._connect_button.bind_property("visible", builder.get_object("disconnect_button"), "visible", 4)
self._bookmarks_button = builder.get_object("bookmarks_button")
self._bookmarks_button.bind_property("active", builder.get_object("bookmarks_box"), "visible")
# Force Ctrl
self._ftp_view.connect("key-press-event", self._app.force_ctrl)
self._file_view.connect("key-press-event", self._app.force_ctrl)
@@ -218,6 +226,7 @@ class FtpClientBox(Gtk.HBox):
@run_task
def init_ftp(self):
self.init_bookmarks()
GLib.idle_add(self._ftp_model.clear)
try:
if self._ftp:
@@ -291,7 +300,7 @@ class FtpClientBox(Gtk.HBox):
self._ftp_model.append(File(None, self.ROOT, None, None, self._ftp.pwd(), "0"))
for f in files:
f_data = f.split()
f_data = self._ftp.get_file_data(f)
f_type = f_data[0][0]
is_dir = f_type == "d"
is_link = f_type == "l"
@@ -308,7 +317,7 @@ class FtpClientBox(Gtk.HBox):
r_size = self.get_size_from_bytes(size)
date = f"{f_data[5]}, {f_data[6]} {f_data[7]}"
self._ftp_model.append(File(icon, " ".join(f_data[8:]), r_size, date, f_data[0], size))
self._ftp_model.append(File(icon, f_data[8], r_size, date, f_data[0], size))
def on_connect(self, item=None):
self.init_ftp()
@@ -326,6 +335,10 @@ class FtpClientBox(Gtk.HBox):
if size == self.FOLDER or f_path == self.ROOT:
self.init_ftp_data(f_path)
elif size == self.LINK:
name, sep, f_path = f_path.partition("->")
if f_path:
self.init_ftp_data(f_path.strip())
else:
b_size = row[self.Column.EXTRA]
if b_size.isdigit() and int(b_size) > self.MAX_SIZE:
@@ -712,6 +725,27 @@ class FtpClientBox(Gtk.HBox):
self._ftp_info_label.set_text(message)
self._ftp_info_label.set_tooltip_text(message)
# **************** Bookmarks ***************** #
@run_idle
def init_bookmarks(self):
self._bookmark_model.clear()
list(map(lambda b: self._bookmark_model.append((b,)), self._settings.ftp_bookmarks))
def on_bookmark_activated(self, view, path, column):
self.init_ftp_data(self._bookmark_model[path][0])
def on_bookmark_add(self, item=None):
self._bookmarks_button.set_active(True)
self._bookmark_model.append((self._ftp_model.get_value(self._ftp_model.get_iter_first(), 4),))
self._settings.ftp_bookmarks = [r[0] for r in self._bookmark_model]
def on_bookmark_remove(self, item=None):
model, paths = self._bookmark_view.get_selection().get_selected_rows()
if paths and show_dialog(DialogType.QUESTION, self._app.app_window) == Gtk.ResponseType.OK:
list(map(lambda p: model.remove(model.get_iter(p)), paths))
self._settings.ftp_bookmarks = [r[0] for r in self._bookmark_model]
def on_view_key_press(self, view, event):
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
@@ -743,6 +777,8 @@ class FtpClientBox(Gtk.HBox):
self.on_ftp_file_remove()
elif self._file_view.is_focus():
self.on_file_remove()
elif self._bookmark_view.is_focus():
self.on_bookmark_remove()
elif key is KeyboardKey.RETURN:
path, column = view.get_cursor()
if path:
@@ -760,11 +796,13 @@ class FtpClientBox(Gtk.HBox):
# Enable selection.
self._select_enabled = True
def on_paned_size_allocate(self, paned, allocation):
@staticmethod
def on_paned_size_allocate(paned, allocation):
""" Sets default homogeneous sizes. """
paned.set_position(0.5 * allocation.width)
def get_size_from_bytes(self, size):
@staticmethod
def get_size_from_bytes(size):
""" Simple convert function from bytes to other units like K, M or G. """
try:
b = float(size)

View File

@@ -1,3 +1,31 @@
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Author: Dmitriy Yefremov
#
from contextlib import suppress
from pathlib import Path
@@ -5,7 +33,7 @@ from app.commons import run_idle, log
from app.eparser import get_bouquets, get_services, BouquetsReader
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neutrino_bouquets
from app.settings import SettingsType
from app.settings import SettingsType, IS_DARWIN, SEP
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message, get_builder
from app.ui.main_helper import on_popup_menu
from .uicommons import Gtk, UI_RESOURCES_PATH, KeyboardKey, Column
@@ -19,8 +47,8 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
profile = settings.setting_type
if profile is SettingsType.ENIGMA_2:
pattern = ".{}".format(bq_type.value)
f_pattern = "userbouquet.*{}".format(pattern)
pattern = f".{bq_type.value}"
f_pattern = f"{'' if IS_DARWIN else 'userbouquet.'}*{pattern}"
elif profile is SettingsType.NEUTRINO_MP:
pattern = "webtv.xml" if bq_type is BqType.WEBTV else "bouquets.xml"
f_pattern = "bouquets.xml"
@@ -38,6 +66,10 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
return
if profile is SettingsType.ENIGMA_2:
if IS_DARWIN and file_path.rfind("userbouquet.") < 0:
show_dialog(DialogType.ERROR, transient, text="Not allowed in this context!")
return
bq = get_enigma2_bouquet(file_path)
imported = list(filter(lambda x: x.data in services or x.type is BqServiceType.IPTV, bq.services))
@@ -55,7 +87,7 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
bqs = parse_webtv(file_path, "WEBTV", bq_type.value)
else:
bqs = get_neutrino_bouquets(file_path, "", bq_type.value)
file_path = "{}/".format(Path(file_path).parent)
file_path = f"{Path(file_path).parent}{SEP}"
ImportDialog(transient, file_path, settings, services.keys(), lambda b, s: appender(b), (bqs,)).show()

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
<!-- Generated with glade 3.22.2
The MIT License (MIT)
Copyright (c) 2018-2021 Dmitriy Yefremov
Copyright (c) 2018-2022 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
@@ -64,7 +64,6 @@ Author: Dmitriy Yefremov
</object>
<object class="GtkDialog" id="search_unavailable_streams_dialog">
<property name="use-header-bar">1</property>
<property name="width_request">320</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes"> </property>
<property name="resizable">False</property>
@@ -103,76 +102,98 @@ Author: Dmitriy Yefremov
<property name="label_yalign">1</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid">
<object class="GtkBox" id="search_unavailable_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_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="column_spacing">10</property>
<property name="spacing">10</property>
<child>
<object class="GtkBox">
<object class="GtkGrid" id="search_unavailable_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel">
<object class="GtkBox" id="found_state_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Found</property>
<property name="halign">center</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Found</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="streams_rows_counter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">unavailable streams.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="streams_rows_counter_label">
<object class="GtkLevelBar" id="unavailable_streams_level_bar">
<property name="height_request">10</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
<property name="valign">center</property>
<property name="inverted">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">unavailable streams.</property>
<property name="label" translatable="yes">Please wait, streams testing in progress...</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLevelBar" id="unavailable_streams_level_bar">
<property name="height_request">10</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="inverted">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
@@ -181,6 +202,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Cancel</property>
<property name="valign">center</property>
<child>
<object class="GtkImage" id="cancel_image">
<property name="visible">True</property>
@@ -190,27 +212,12 @@ Author: Dmitriy Yefremov
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Please wait, streams testing in progress...</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="label_item">

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -44,7 +44,7 @@ from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID
from app.settings import SettingsType
from app.tools.yt import YouTubeException, YouTube
from app.ui.dialogs import Action, show_dialog, DialogType, get_message, get_builder
from app.ui.main_helper import get_base_model, get_iptv_url, on_popup_menu, get_picon_pixbuf
from app.ui.main_helper import get_iptv_url, on_popup_menu, get_picon_pixbuf
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon)
_DIGIT_ENTRY_NAME = "digit-entry"
@@ -77,7 +77,7 @@ def get_stream_type(box):
class IptvDialog:
def __init__(self, transient, view, services, bouquet, settings, action=Action.ADD):
def __init__(self, app, view, bouquet=None, service=None, action=Action.ADD):
handlers = {"on_response": self.on_response,
"on_entry_changed": self.on_entry_changed,
"on_url_changed": self.on_url_changed,
@@ -86,11 +86,11 @@ class IptvDialog:
"on_yt_quality_changed": self.on_yt_quality_changed,
"on_info_bar_close": self.on_info_bar_close}
self._app = app
self._action = action
self._s_type = settings.setting_type
self._settings = settings
self._settings = app.app_settings
self._s_type = self._settings.setting_type
self._bouquet = bouquet
self._services = services
self._yt_links = None
self._yt_dl = None
@@ -98,7 +98,7 @@ class IptvDialog:
objects=("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
self._dialog = builder.get_object("iptv_dialog")
self._dialog.set_transient_for(transient)
self._dialog.set_transient_for(app.app_window)
self._name_entry = builder.get_object("name_entry")
self._description_entry = builder.get_object("description_entry")
self._url_entry = builder.get_object("url_entry")
@@ -142,7 +142,7 @@ class IptvDialog:
self.update_reference_entry()
self._stream_type_combobox.set_active(1)
elif self._action is Action.EDIT:
self._current_srv = get_base_model(self._model)[self._paths][:]
self._current_srv = service
self.init_data(self._current_srv)
def show(self):
@@ -167,8 +167,8 @@ class IptvDialog:
self._dialog.destroy()
def init_data(self, srv):
name, fav_id = srv[2], srv[7]
self._name_entry.set_text(name)
fav_id = srv.fav_id
self._name_entry.set_text(srv.service)
self.init_enigma2_data(fav_id) if self._s_type is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id)
def init_enigma2_data(self, fav_id):
@@ -307,11 +307,12 @@ class IptvDialog:
int(self._namespace_entry.get_text()),
quote(self._url_entry.get_text()),
name, name)
self.update_bouquet_data(name, fav_id)
def save_neutrino_data(self):
if self._action is Action.EDIT:
id_data = self._current_srv[7].split("::")
id_data = self._current_srv.fav_id.split("::")
else:
id_data = ["", "", "0", None, None, None, None, "", "", "1"]
id_data[0] = self._url_entry.get_text()
@@ -320,20 +321,25 @@ class IptvDialog:
self._dialog.destroy()
def update_bouquet_data(self, name, fav_id):
picon_id = f"{self._reference_entry.get_text().replace(':', '_')}.png"
if self._action is Action.EDIT:
old_srv = self._services.pop(self._current_srv[7])
self._services[fav_id] = old_srv._replace(service=name, fav_id=fav_id)
self._bouquet[self._paths[0][0]] = fav_id
self._model.set(self._model.get_iter(self._paths), {Column.FAV_SERVICE: name, Column.FAV_ID: fav_id})
services = self._app.current_services
old_srv = services.pop(self._current_srv.fav_id)
new_service = old_srv._replace(service=name, fav_id=fav_id, picon_id=picon_id)
services[fav_id] = new_service
self._app.emit("iptv-service-edited", (old_srv, new_service))
else:
aggr = [None] * 10
aggr = [None] * 8
s_type = BqServiceType.IPTV.name
srv = (None, None, name, None, None, s_type, None, fav_id, *aggr[0:3])
itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
srv) if self._paths else self._model.insert(0, srv)
self._model.set_value(itr, 1, IPTV_ICON)
self._bouquet.insert(self._model.get_path(itr)[0], fav_id)
self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None)
service = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, None, picon_id, *aggr, fav_id, None)
self._app.current_services[fav_id] = service
self._app.emit("iptv-service-added", (service,))
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
@@ -373,9 +379,13 @@ class SearchUnavailableDialog:
@run_task
def do_search(self):
import concurrent.futures
import ssl
import certifi
context = ssl.create_default_context(cafile=certifi.where())
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = {executor.submit(self.get_unavailable, row): row for row in self._iptv_rows}
futures = {executor.submit(self.get_unavailable, row, context): row for row in self._iptv_rows}
for future in concurrent.futures.as_completed(futures):
if not self._download_task:
executor.shutdown()
@@ -384,13 +394,13 @@ class SearchUnavailableDialog:
self._download_task = False
self.on_close()
def get_unavailable(self, row):
def get_unavailable(self, row, context):
if not self._download_task:
return
try:
req = Request(get_iptv_url(row, self._s_type))
self.update_bar()
urlopen(req, timeout=2)
urlopen(req, context=context, timeout=2)
except HTTPError as e:
if e.code != 403:
self.append_data(row)
@@ -833,7 +843,7 @@ class M3uImportDialog(IptvListDialog):
class YtListImportDialog:
def __init__(self, transient, settings, appender):
def __init__(self, app):
handlers = {"on_import": self.on_import,
"on_receive": self.on_receive,
"on_yt_url_entry_changed": self.on_url_entry_changed,
@@ -845,12 +855,13 @@ class YtListImportDialog:
"on_key_press": self.on_key_press,
"on_close": self.on_close}
self.appender = appender
self._s_type = settings.setting_type
# self._main_window, self._settings, self.append_imported_services
self.appender = app.append_imported_services
self._settings = app.app_settings
self._s_type = self._settings.setting_type
self._download_task = False
self._yt_list_id = None
self._yt_list_title = None
self._settings = settings
self._yt = None
builder = get_builder(_UI_PATH, handlers, use_str=True,
@@ -859,7 +870,7 @@ class YtListImportDialog:
"yt_import_image"))
self._dialog = builder.get_object("yt_import_dialog_window")
self._dialog.set_transient_for(transient)
self._dialog.set_transient_for(app.app_window)
self._list_view_scrolled_window = builder.get_object("yt_list_view_scrolled_window")
self._model = builder.get_object("yt_liststore")
self._progress_bar = builder.get_object("yt_progress_bar")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -360,7 +360,7 @@ def has_locked_hide(model, paths, col_num):
# ***************** Location *******************#
def locate_in_services(fav_view, services_view, parent_window):
def locate_in_services(fav_view, services_view, column, parent_window):
""" Locating and scrolling to the service """
model, paths = fav_view.get_selection().get_selected_rows()
@@ -372,7 +372,7 @@ def locate_in_services(fav_view, services_view, parent_window):
fav_id = model.get_value(model.get_iter(paths[0]), Column.FAV_ID)
for index, row in enumerate(services_view.get_model()):
if row[Column.SRV_FAV_ID] == fav_id:
if row[column] == fav_id:
scroll_to(index, services_view)
break
@@ -686,9 +686,9 @@ def append_text_to_tview(char, view):
view.scroll_to_mark(insert, 0.0, True, 0.0, 1.0)
def get_iptv_url(row, s_type):
def get_iptv_url(row, s_type, column=Column.FAV_ID):
""" Returns url from iptv type row """
data = row[Column.FAV_ID].split(":" if s_type is SettingsType.ENIGMA_2 else "::")
data = row[column].split(":" if s_type is SettingsType.ENIGMA_2 else "::")
if s_type is SettingsType.ENIGMA_2:
data = list(filter(lambda x: "http" in x, data))
if data:

View File

@@ -377,7 +377,7 @@ class PiconManager(Gtk.Box):
return
itr_str, sep, src = txt.partition(self._app.DRAG_SEP)
if src == self._app.BQ_MODEL_NAME:
if src == self._app.BQ_MODEL:
return
path, pos = view.get_dest_row_at_pos(x, y) or (None, None)
@@ -385,7 +385,7 @@ class PiconManager(Gtk.Box):
return
model = view.get_model()
if src == self._app.FAV_MODEL_NAME:
if src == self._app.FAV_MODEL:
target_view = self._app.fav_view
c_id = Column.FAV_ID
else:

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -57,6 +57,8 @@ class PlayerBox(Gtk.Box):
self._app = app
self._app.connect("fav-clicked", self.on_fav_clicked)
self._app.connect("srv-clicked", self.on_srv_clicked)
self._app.connect("iptv-clicked", self.on_iptv_clicked)
self._app.connect("page-changed", self.on_page_changed)
self._app.connect("play-current", self.on_play_current)
self._app.connect("play-recording", self.on_play_recording)
@@ -112,6 +114,42 @@ class PlayerBox(Gtk.Box):
elif mode is FavClickMode.PLAY:
self.on_play_service()
def on_srv_clicked(self, app, mode):
if not self._app.http_api:
return
view = self._app.services_view
path, column = view.get_cursor()
if path:
srv = self._app.current_services.get(view.get_model()[path][Column.SRV_FAV_ID], None)
if not srv or not srv.picon_id:
return
ref = self._app.get_service_ref_data(srv)
s_type = self._app.app_settings.setting_type
error_msg = "No connection to the receiver!"
if s_type is SettingsType.ENIGMA_2:
def zap(rq):
self.on_watch() if rq and rq.get("e2state", False) else self.on_error(None, error_msg)
self._app.http_api.send(HttpAPI.Request.ZAP, ref, zap)
elif self._s_type is SettingsType.NEUTRINO_MP:
def zap(rq):
self.on_watch() if rq and rq.get("data", None) == "ok" else self.on_error(None, error_msg)
self._app.http_api.send(HttpAPI.Request.N_ZAP, f"?{ref}", zap)
def on_iptv_clicked(self, app, mode):
if not self._app.http_api:
return
view = self._app.iptv_services_view
path, column = view.get_cursor()
if path:
row = view.get_model()[path][:]
url = get_iptv_url(row, self._app.app_settings.setting_type, Column.IPTV_FAV_ID)
self.play(url, row[Column.IPTV_SERVICE]) if url else self.on_error(None, "No reference is present!")
def on_play_current(self, app, url):
self.on_watch()
@@ -266,7 +304,7 @@ class PlayerBox(Gtk.Box):
if click_mode is FavClickMode.PLAY:
self.on_play_service()
elif click_mode is FavClickMode.ZAP_PLAY:
self.on_zap(self.on_watch)
self._app.on_zap(self.on_watch)
elif click_mode is FavClickMode.STREAM:
self.on_play_stream()
@@ -302,7 +340,7 @@ class PlayerBox(Gtk.Box):
widget.set_size_request(w * 0.6, -1)
@run_idle
def show_playback_window(self):
def show_playback_window(self, title=None):
width, height = 480, 240
size = self._app.app_settings.get("playback_window_size")
if size:
@@ -310,9 +348,9 @@ class PlayerBox(Gtk.Box):
if self._playback_window:
self._playback_window.show()
self._playback_window.set_title(self.get_playback_title())
self._playback_window.set_title(title or self.get_playback_title())
else:
self._playback_window = Gtk.Window(title=self.get_playback_title(),
self._playback_window = Gtk.Window(title=title or self.get_playback_title(),
window_position=Gtk.WindowPosition.CENTER,
icon_name="demon-editor")
@@ -381,7 +419,7 @@ class PlayerBox(Gtk.Box):
url = self._app.get_url_from_m3u(data)
GLib.timeout_add_seconds(1, self.play, url) if url else self.on_error(None, "Can't Playback!")
def play(self, url):
def play(self, url, title=None):
if self._play_mode is PlayStreamsMode.M3U:
self._app.save_stream_to_m3u(url)
return
@@ -393,7 +431,7 @@ class PlayerBox(Gtk.Box):
if self._play_mode is PlayStreamsMode.BUILT_IN:
self.show()
elif self._play_mode is PlayStreamsMode.WINDOW:
self.show_playback_window()
self.show_playback_window(title)
if self._player:
self.emit("play", url)

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2021 Dmitriy Yefremov
Copyright (c) 2018-2022 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -399,7 +399,7 @@ Author: Dmitriy Yefremov
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="column_spacing">2</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="label11">
<property name="visible">True</property>
@@ -579,7 +579,7 @@ Author: Dmitriy Yefremov
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="label_xalign">0.02</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="tr_box">
@@ -594,7 +594,7 @@ Author: Dmitriy Yefremov
<object class="GtkGrid" id="tr_dialog_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
@@ -666,7 +666,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">10</property>
<property name="max_width_chars">14</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
@@ -685,7 +685,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">10</property>
<property name="max_width_chars">14</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="placeholder_text" translatable="yes">27500000</property>
<property name="input_purpose">digits</property>
@@ -703,7 +703,7 @@ Author: Dmitriy Yefremov
<property name="model">pol_store</property>
<property name="id_column">0</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext3"/>
<object class="GtkCellRendererText" id="pol_cellrenderertext"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
@@ -784,9 +784,9 @@ Author: Dmitriy Yefremov
<object class="GtkGrid" id="tr_dialog_grid2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="label7">
<object class="GtkLabel" id="tr_pls_mode_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pls mode</property>
@@ -797,7 +797,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkLabel" id="label8">
<object class="GtkLabel" id="tr_pls_code_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pls code</property>
@@ -808,7 +808,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkLabel" id="label9">
<object class="GtkLabel" id="id_id_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Is ID</property>
@@ -870,6 +870,34 @@ Author: Dmitriy Yefremov
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="tr_t2mi_plp_id_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">T2-MI PLP ID</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="t2mi_plp_id_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">0 - 255</property>
<property name="input_purpose">digits</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
</child>
<child type="label">
@@ -929,6 +957,8 @@ Author: Dmitriy Yefremov
<column type="gchararray"/>
<!-- column-name is_id -->
<column type="gchararray"/>
<!-- column-name t2mi_plp_id -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkBox" id="satellite_editor_box">
@@ -1310,6 +1340,18 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="t2mi_plp_id_column">
<property name="visible">False</property>
<property name="title" translatable="yes">T2-MI PLP ID</property>
<child>
<object class="GtkCellRendererText" id="t2mi_plp_id_cellrenderertext"/>
<attributes>
<attribute name="text">9</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>

View File

@@ -304,6 +304,7 @@ class TransponderDialog:
self._pls_mode_box = builder.get_object("pls_mode_box")
self._pls_code_entry = builder.get_object("pls_code_entry")
self._is_id_entry = builder.get_object("is_id_entry")
self._t2mi_plp_id_entry = builder.get_object("t2mi_plp_id_entry")
# pattern for frequency and rate entries (only digits)
self._pattern = re.compile(r"\D")
# style
@@ -336,6 +337,7 @@ class TransponderDialog:
self._pls_mode_box.set_active_id(PLS_MODE.get(transponder.pls_mode, None))
self._is_id_entry.set_text(transponder.is_id if transponder.is_id else "")
self._pls_code_entry.set_text(transponder.pls_code if transponder.pls_code else "")
self._t2mi_plp_id_entry.set_text(transponder.t2mi_plp_id if transponder.t2mi_plp_id else "")
def to_transponder(self):
return Transponder(frequency=self._freq_entry.get_text(),
@@ -346,7 +348,8 @@ class TransponderDialog:
modulation=self._mod_box.get_active_id(),
pls_mode=get_key_by_value(PLS_MODE, self._pls_mode_box.get_active_id()),
pls_code=self._pls_code_entry.get_text(),
is_id=self._is_id_entry.get_text())
is_id=self._is_id_entry.get_text(),
t2mi_plp_id=self._t2mi_plp_id_entry.get_text())
def on_entry_changed(self, entry):
entry.set_name("digit-entry" if self._pattern.search(entry.get_text()) else "GtkEntry")
@@ -360,6 +363,8 @@ class TransponderDialog:
return False
elif self._pattern.search(tr.pls_code) or self._pattern.search(tr.is_id):
return False
elif self._pattern.search(tr.t2mi_plp_id):
return False
return True

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2022 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -62,7 +62,7 @@ class ServiceDetailsDialog:
_DIGIT_ENTRY_NAME = "digit-entry"
def __init__(self, transient, settings, srv_view, fav_view, services, bouquets, new_color, action=Action.EDIT):
def __init__(self, app, new_color, action=Action.EDIT):
handlers = {"on_system_changed": self.on_system_changed,
"on_save": self.on_save,
"on_create_new": self.on_create_new,
@@ -76,19 +76,19 @@ class ServiceDetailsDialog:
builder = get_builder(_UI_PATH, handlers, use_str=True)
self._builder = builder
settings = app.app_settings
self._dialog = builder.get_object("service_details_dialog")
self._dialog.set_transient_for(transient)
self._dialog.set_transient_for(app.app_window)
self._s_type = settings.setting_type
self._tr_type = TrType.Satellite
self._satellites_xml_path = settings.profile_data_path + "satellites.xml"
self._picons_path = settings.profile_picons_path
self._services_view = srv_view
self._fav_view = fav_view
self._services_view = app.services_view
self._fav_view = app.fav_view
self._action = action
self._old_service = None
self._services = services
self._bouquets = bouquets
self._services = app.current_services
self._bouquets = app.current_bouquets
self._new_color = new_color
self._transponder_services_iters = None
self._current_model = None

File diff suppressed because it is too large Load Diff

View File

@@ -37,10 +37,6 @@ from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT, IS_GNOME_SESSION
def show_settings_dialog(transient, options):
return SettingsDialog(transient, options).show()
class SettingsDialog:
_DIGIT_ENTRY_NAME = "digit-entry"
_DIGIT_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
@@ -149,14 +145,12 @@ class SettingsDialog:
self._audio_codec_combo_box = builder.get_object("audio_codec_combo_box")
self._transcoding_switch.bind_property("active", builder.get_object("record_box"), "sensitive")
self._edit_preset_switch.bind_property("active", self._apply_presets_button, "sensitive")
self._edit_preset_switch.bind_property("active", builder.get_object("video_options_frame"), "sensitive")
self._edit_preset_switch.bind_property("active", builder.get_object("audio_options_frame"), "sensitive")
self._play_in_built_radio_button = builder.get_object("play_in_built_radio_button")
self._play_in_window_radio_button = builder.get_object("play_in_window_radio_button")
self._get_m3u_radio_button = builder.get_object("get_m3u_radio_button")
self._gst_lib_button = builder.get_object("gst_lib_button")
self._vlc_lib_button = builder.get_object("vlc_lib_button")
self._mpv_lib_button = builder.get_object("mpv_lib_button")
self._edit_preset_switch.bind_property("active", builder.get_object("video_options_grid"), "sensitive")
self._edit_preset_switch.bind_property("active", builder.get_object("audio_options_grid"), "sensitive")
self._play_streams_combo_box = builder.get_object("play_streams_combo_box")
self._stream_lib_combo_box = builder.get_object("stream_lib_combo_box")
self._double_click_combo_box = builder.get_object("double_click_combo_box")
self._allow_main_list_playback_switch = builder.get_object("allow_main_list_playback_switch")
# Program.
self._before_save_switch = builder.get_object("before_save_switch")
self._before_downloading_switch = builder.get_object("before_downloading_switch")
@@ -177,13 +171,6 @@ class SettingsDialog:
self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
self._enable_update_yt_dl_switch = builder.get_object("enable_update_yt_dl_switch")
self._enable_send_to_switch = builder.get_object("enable_send_to_switch")
self._click_mode_disabled_button = builder.get_object("click_mode_disabled_button")
self._click_mode_stream_button = builder.get_object("click_mode_stream_button")
self._click_mode_play_button = builder.get_object("click_mode_play_button")
self._click_mode_zap_button = builder.get_object("click_mode_zap_button")
self._click_mode_zap_and_play_button = builder.get_object("click_mode_zap_and_play_button")
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_play_button, "sensitive")
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_zap_and_play_button, "sensitive")
# EXPERIMENTAL.
self._enable_exp_switch = builder.get_object("enable_experimental_switch")
self._enable_exp_switch.bind_property("active", builder.get_object("yt_dl_box"), "sensitive")
@@ -192,9 +179,9 @@ class SettingsDialog:
self._enable_exp_switch.bind_property("active", builder.get_object("enable_direct_playback_box"), "sensitive")
# Enigma2 only.
self._enigma_radio_button.bind_property("active", builder.get_object("bq_naming_grid"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("enable_experimental_box"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("experimental_box"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("allow_double_click_box"), "sensitive")
# Profiles.
self._profile_view = builder.get_object("profile_tree_view")
self._profile_add_button = builder.get_object("profile_add_button")
@@ -245,7 +232,6 @@ class SettingsDialog:
self._neutrino_radio_button.set_active(self._s_type is SettingsType.NEUTRINO_MP)
self.update_picon_paths()
self.update_title()
self._click_mode_zap_button.set_sensitive(self._support_http_api_switch.get_active())
self._lang_combo_box.set_active_id(self._ext_settings.language)
self.on_info_bar_close() if is_enigma_profile else self.show_info_message(
"The Neutrino has only experimental support. Not all features are supported!", Gtk.MessageType.WARNING)
@@ -323,9 +309,10 @@ class SettingsDialog:
self._record_data_path_field.set_text(self._settings.records_path)
self._before_save_switch.set_active(self._settings.backup_before_save)
self._before_downloading_switch.set_active(self._settings.backup_before_downloading)
self.set_fav_click_mode(self._settings.fav_click_mode)
self.set_play_stream_mode(self._settings.play_streams_mode)
self.set_stream_lib(self._settings.stream_lib)
self._play_streams_combo_box.set_active(self._settings.play_streams_mode.value)
self._stream_lib_combo_box.set_active_id(self._settings.stream_lib)
self._double_click_combo_box.set_active_id(str(self._settings.fav_click_mode))
self._allow_main_list_playback_switch.set_active(self._settings.main_list_playback)
self._load_on_startup_switch.set_active(self._settings.load_last_config)
self._bouquet_hints_switch.set_active(self._settings.show_bq_hints)
self._services_hints_switch.set_active(self._settings.show_srv_hints)
@@ -386,9 +373,10 @@ class SettingsDialog:
self._ext_settings.profiles = self._settings.profiles
self._ext_settings.backup_before_save = self._before_save_switch.get_active()
self._ext_settings.backup_before_downloading = self._before_downloading_switch.get_active()
self._ext_settings.fav_click_mode = self.get_fav_click_mode()
self._ext_settings.play_streams_mode = self.get_play_stream_mode()
self._ext_settings.stream_lib = self.get_stream_lib()
self._ext_settings.play_streams_mode = PlayStreamsMode(self._play_streams_combo_box.get_active())
self._ext_settings.stream_lib = self._stream_lib_combo_box.get_active_id()
self._ext_settings.fav_click_mode = int(self._double_click_combo_box.get_active_id())
self._ext_settings.main_list_playback = self._allow_main_list_playback_switch.get_active()
self._ext_settings.language = self._lang_combo_box.get_active_id()
self._ext_settings.load_last_config = self._load_on_startup_switch.get_active()
self._ext_settings.show_bq_hints = self._bouquet_hints_switch.get_active()
@@ -494,11 +482,8 @@ class SettingsDialog:
self._colors_grid.set_sensitive(state)
def on_http_mode_switch(self, switch, state):
self._click_mode_zap_button.set_sensitive(state)
if any((self._click_mode_play_button.get_active(),
self._click_mode_zap_button.get_active(),
self._click_mode_zap_and_play_button.get_active())):
self._click_mode_disabled_button.set_active(True)
if self._main_stack.get_visible_child_name() == "program" and not state:
self.show_info_message("May affect some features availability! ", Gtk.MessageType.WARNING)
def on_experimental_switch(self, switch, state):
if not state:
@@ -528,7 +513,7 @@ class SettingsDialog:
name = "profile"
while name in self._profiles:
count += 1
name = "profile{}".format(count)
name = f"profile{count}"
self._profiles[name] = self._s_type.get_default_settings()
model.append((name, None))
@@ -637,78 +622,26 @@ class SettingsDialog:
self._settings.http_port = port
def on_click_mode_togged(self, button):
if self._main_stack.get_visible_child_name() != "extra":
if self._main_stack.get_visible_child_name() != "streaming":
return
mode = self.get_fav_click_mode()
mode = FavClickMode(int(self._double_click_combo_box.get_active_id()))
if mode is FavClickMode.PLAY:
self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
elif mode is FavClickMode.STREAM:
self.show_info_message("Playback IPTV streams only!", Gtk.MessageType.WARNING)
elif mode is FavClickMode.DISABLED:
self._allow_main_list_playback_switch.set_active(False)
else:
self.on_info_bar_close()
@run_idle
def set_fav_click_mode(self, mode):
mode = FavClickMode(mode)
self._click_mode_disabled_button.set_active(mode is FavClickMode.DISABLED)
self._click_mode_stream_button.set_active(mode is FavClickMode.STREAM)
self._click_mode_play_button.set_active(mode is FavClickMode.PLAY)
self._click_mode_zap_button.set_active(mode is FavClickMode.ZAP)
self._click_mode_zap_and_play_button.set_active(mode is FavClickMode.ZAP_PLAY)
def get_fav_click_mode(self):
if self._click_mode_zap_button.get_active():
return FavClickMode.ZAP
if self._click_mode_play_button.get_active():
return FavClickMode.PLAY
if self._click_mode_zap_and_play_button.get_active():
return FavClickMode.ZAP_PLAY
if self._click_mode_stream_button.get_active():
return FavClickMode.STREAM
return FavClickMode.DISABLED
self._allow_main_list_playback_switch.set_sensitive(mode is not FavClickMode.DISABLED)
def on_play_mode_changed(self, button):
if self._main_stack.get_visible_child_name() != "streaming" or not button.get_active():
if self._main_stack.get_visible_child_name() != "streaming":
return
if self._settings.is_darwin:
is_gst = self._gst_lib_button.get_active()
self._play_in_built_radio_button.set_sensitive(is_gst)
self._play_in_window_radio_button.set_active(not is_gst and self._play_in_built_radio_button.get_active())
if button.get_active():
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
@run_idle
def set_play_stream_mode(self, mode):
self._play_in_built_radio_button.set_active(mode is PlayStreamsMode.BUILT_IN)
self._play_in_window_radio_button.set_active(mode is PlayStreamsMode.WINDOW)
self._get_m3u_radio_button.set_active(mode is PlayStreamsMode.M3U)
if self._settings.is_darwin and self._settings.stream_lib != "gst":
self._play_in_built_radio_button.set_sensitive(False)
def get_play_stream_mode(self):
if self._play_in_built_radio_button.get_active():
return PlayStreamsMode.BUILT_IN
if self._play_in_window_radio_button.get_active():
return PlayStreamsMode.WINDOW
if self._get_m3u_radio_button.get_active():
return PlayStreamsMode.M3U
return self._settings.play_streams_mode
def set_stream_lib(self, mode):
self._vlc_lib_button.set_active(mode == "vlc")
self._gst_lib_button.set_active(mode == "gst")
self._mpv_lib_button.set_active(mode == "mpv")
def get_stream_lib(self):
if self._gst_lib_button.get_active():
return "gst"
elif self._vlc_lib_button.get_active():
return "vlc"
return "mpv"
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
def on_transcoding_preset_changed(self, button):
presets = self._settings.transcoding_presets

View File

@@ -1,3 +1,7 @@
* {
-GtkDialog-action-area-border: 6;
}
#digit-entry {
border-color: Red;
}
@@ -10,12 +14,24 @@
margin: 1px;
}
#stack-switch-button {
padding-top: 0;
padding-bottom: 0;
}
paned > separator {
background-repeat: no-repeat;
background-position: center;
}
paned.horizontal > separator {
background-size: 2px 24px;
}
paned.vertical > separator {
background-size: 24px 2px;
}
.red-button {
background-image: none;
background-color: red;

View File

@@ -189,6 +189,7 @@ class ViewTarget(Enum):
BOUQUET = 0
FAV = 1
SERVICES = 2
IPTV = 3
class BqGenType(Enum):
@@ -259,6 +260,13 @@ class Column(IntEnum):
REC_LEN = 3
REC_FILE = 4
REC_DESC = 5
# IPTV view
IPTV_SERVICE = 0
IPTV_TYPE = 1
IPTV_PICON = 2
IPTV_REF = 3
IPTV_FAV_ID = 4
IPTV_PICON_ID = 5
def __index__(self):
""" Overridden to get the index in slices directly """

View File

@@ -1,16 +1,11 @@
* {
-GtkDialog-action-area-border: 6;
}
button {
min-height: 24px;
min-width: 24px;
}
entry {
min-height: 24px;
-GtkDialog-action-area-border: 12;
}
switch {
margin-right: 2px;
margin-right: 2px;
}
spinbutton entry {
min-height: 16px;
}

View File

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

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 2.1.1-Beta
Version: 2.2.1-Beta
Section: utils
Priority: optional
Architecture: all
@@ -8,9 +8,18 @@ Depends: python3 (>= 3.6),
python3-requests,
python3-gi,
python3-gi-cairo,
gir1.2-notify-0.7
gir1.2-notify-0.7,
p7zip-full
Recommends: libmpv1,
python3-chardet,
libgtksourceview (>= 3.0)
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
Homepage: https://dyefremov.github.io/DemonEditor
Description: Enigma2 channel and satellite list editor
Editing bouquets, channels, satellites, importing services,
downloading picons and updating satellites from the Web,
extended support of IPTV, assignment of EPG from DVB or
XML for IPTV services, playback of IPTV or other streams
directly from the bouquet list, control panel (via HTTP API),
ability to view EPG and manage timers (via HTTP API),
simple FTP client (experimental).

View File

@@ -5,6 +5,9 @@ Comment=Channel and satellite list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
Comment[it]=Editor di liste canali e satelliti per Enigma2
Comment[tr]=Enigma2 için kanal ve uydu listesi editörü
Comment[es]=Editor de listas de canales y satélites para Enigma2
Icon=demon-editor
Exec=/usr/bin/demon-editor
Terminal=false

View File

@@ -69,7 +69,7 @@ app = BUNDLE(coll,
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
'LSApplicationCategoryType': 'public.app-category.utilities',
'LSMinimumSystemVersion': '10.13',
'CFBundleShortVersionString': f"2.1.1.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2021, Dmitriy Yefremov",
'CFBundleShortVersionString': f"2.2.1.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2022, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false'
})

View File

@@ -1283,6 +1283,9 @@ msgstr "Аўтаматычная ўсталёўка імя са спіса аб
msgid "Playback"
msgstr "Прайграванне"
msgid "Playback:"
msgstr "Прайграванне:"
msgid "Audio"
msgstr "Аўдыё"
@@ -1333,3 +1336,21 @@ msgstr "Зменена месцаванне элементаў!"
msgid "Restart the program to apply all changes."
msgstr "Перазапусціце праграму, каб ужыць усе змены."
msgid "New folder"
msgstr "Стварыць тэчку"
msgid "Rename"
msgstr "Змяніць назву"
msgid "Bookmarks"
msgstr "Закладкі"
msgid "Add bookmark"
msgstr "Дадаць закладку"
msgid "All bouquets"
msgstr "Усе букеты"
msgid "Playback from the main list"
msgstr "Прайграванне з асноўнага спіса"

View File

@@ -935,7 +935,7 @@ msgid "Built-in player"
msgstr "Integrierter Player"
msgid "In a separate window"
msgstr "In einem separaten Fenster"
msgstr "In separatem Fenster"
msgid "Only get m3u file"
msgstr "Nur m3u-Datei erhalten"
@@ -1297,8 +1297,11 @@ msgstr "Automatisch den in der Favoritenliste ausgewählten Namen einstellen."
msgid "Playback"
msgstr "Wiedergabe"
msgid "Playback:"
msgstr "Wiedergabe:"
msgid "Audio"
msgstr ""
msgstr "Audio"
msgid "Audio Track"
msgstr "Audio"
@@ -1347,3 +1350,21 @@ msgstr "Das Layout der Elemente wurde geändert!"
msgid "Restart the program to apply all changes."
msgstr "Starte das Programm neu, um alle Änderungen zu übernehmen."
msgid "New folder"
msgstr "Ordner erstellen"
msgid "Rename"
msgstr "Umbenennen"
msgid "Bookmarks"
msgstr "Lesezeichen"
msgid "Add bookmark"
msgstr "Lesezeichen hinzufügen"
msgid "All bouquets"
msgstr "Alle Bouquets"
msgid "Playback from the main list"
msgstr "Wiedergabe aus der Hauptliste"

View File

@@ -1,23 +1,25 @@
# Copyright (C) 2018-2020 Frank Neirynck
# Copyright (C) 2018-2022 Víctor Pont
# This file is distributed under the MIT license.
#
# Frank Neirynck <frank@insink.be>, 2018-2020.
#
# Víctor Pont <victor@pont.cat>, 2021-2022.
msgid ""
msgstr ""
"Last-Translator: Frank Neirynck\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Víctor Pont\n"
"Language-Team: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
"X-Generator: Poedit 3.0.1\n"
msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>"
msgstr ""
"Víctor Pont <victor@pont.cat>\n"
"Frank Neirynck <frank@insink.be>"
# Main
msgid "Service"
@@ -33,19 +35,19 @@ msgid "Picon"
msgstr "Picon"
msgid "Freq"
msgstr "Frec."
msgstr "Frec"
msgid "Rate"
msgstr "Ratio"
msgid "Pol"
msgstr "Pol."
msgstr "Pol"
msgid "System"
msgstr "Sistema"
msgid "Pos"
msgstr "Pos."
msgstr "Pos"
msgid "Num"
msgstr "Núm"
@@ -56,6 +58,9 @@ msgstr "IP actual:"
msgid "Assign"
msgstr "Asignar"
msgid "Assign file"
msgstr "Asignar fichero"
msgid "Bouquet details"
msgstr "Detalles bouquet"
@@ -110,6 +115,9 @@ msgstr "Establecer nombre predeterminado"
msgid "Insert marker"
msgstr "Insertar marcador"
msgid "Insert space"
msgstr "Insertar espacio"
msgid "Locate in services"
msgstr "Buscar en servicios"
@@ -162,10 +170,10 @@ msgid "Satellites downloader"
msgstr "Descarga de satélites"
msgid "Remove"
msgstr "Quitar"
msgstr "Borrar"
msgid "Remove all unavailable"
msgstr "Quitar todo lo indisponible"
msgstr "Borrar lo que no esté disponible"
msgid "Satellites editor"
msgstr "Editor de satélites"
@@ -225,7 +233,7 @@ msgid "Receiver IP:"
msgstr "IP del receptor:"
msgid "Remove unused bouquets"
msgstr "Quitar bouquets sin usar"
msgstr "Borrar bouquets sin usar"
msgid "Reset profile"
msgstr "Restablecer perfil"
@@ -233,6 +241,9 @@ msgstr "Restablecer perfil"
msgid "Satellites"
msgstr "Satélites"
msgid "Transponders"
msgstr "Transponders"
msgid "Satellites.xml file:"
msgstr "Fichero satellites.xml:"
@@ -327,7 +338,7 @@ msgid "Path to Enigma2 picons:"
msgstr "Ruta a picons de Enigma2:"
msgid "Specify the correct position value for the provider!"
msgstr "¡Especifique el valor correcto de la posición del proveedor!"
msgstr "¡Especifica el valor correcto de la posición del proveedor!"
msgid "Converter between name formats"
msgstr "Conversor entre formatos de nombre"
@@ -341,10 +352,9 @@ msgstr "Cargar proveedores de satélite."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Para configurar automáticamente los identificadores para picons,\n"
"cargue primero la lista de servicios requeridos en la ventana principal."
"carga primero la lista de servicios requeridos en la ventana principal."
# Satellites editor
msgid "Satellites edit tool"
@@ -376,7 +386,7 @@ msgid "Satellites update"
msgstr "Actualizar satélites"
msgid "Remove selection"
msgstr "Quitar selección"
msgstr "Borrar selección"
# Service details dialog
msgid "Service data:"
@@ -396,7 +406,7 @@ msgid ""
"Continue?"
msgstr ""
"Los cambios se aplicarán a todos los servicios de este transpondedor!\n"
"¿Continuar?"
"¿Quieres continuar?"
msgid "Reference"
msgstr "Referencia"
@@ -408,7 +418,7 @@ msgid "Flags:"
msgstr "Flags:"
msgid "Delays (ms):"
msgstr "Retraso (ms)"
msgstr "Retraso (ms):"
msgid "Bitstream"
msgstr "Secuencia de bits"
@@ -493,7 +503,7 @@ msgid "Error. No bouquet is selected!"
msgstr "Error. ¡Ningún bouquet seleccionado!"
msgid "This item is not allowed to be removed!"
msgstr "¡Este elemento no puede ser quitado!"
msgstr "¡Este elemento no puede ser borrado!"
msgid "This item is not allowed to edit!"
msgstr "¡Este elemento no puede ser editado!"
@@ -502,7 +512,7 @@ msgid "Not allowed in this context!"
msgstr "¡No permitido en este contexto!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Por favor, descargue ficheros desde el receptor o configure la ruta para leer los datos!"
msgstr "Por favor, descarga ficheros desde el receptor o configura la ruta para leer los datos!"
msgid "Reading data error!"
msgstr "¡Error de lectura de datos!"
@@ -511,17 +521,20 @@ msgid "No m3u file is selected!"
msgstr "¡No se ha seleccionado ningún fichero m3u!"
msgid "Not implemented yet!"
msgstr "¡No implementado!"
msgstr "¡No implementado todavía!"
msgid "The text of marker is empty, please try again!"
msgstr "¡El texto del marcador está vacío, inténtalo de nuevo!"
msgid "Please, select only one item!"
msgstr "¡Por favor, seleccione un único elemento!"
msgstr "¡Por favor, selecciona un único elemento!"
msgid "No png file is selected!"
msgstr "¡No se ha seleccionado ningún fichero png!"
msgid "No profile selected!"
msgstr "¡No se ha seleccionado ningún perfil!"
msgid "No reference is present!"
msgstr "¡Ninguna referencia presente!"
@@ -535,7 +548,7 @@ msgid "Done!"
msgstr "¡Hecho!"
msgid "Please, wait..."
msgstr "Por favor, espere..."
msgstr "Por favor, espera..."
msgid "Resizing..."
msgstr "Redimensionando..."
@@ -550,29 +563,29 @@ msgid "Please, select only one satellite!"
msgstr "¡Seleccione sólo un Satélite!"
msgid "Please check your parameters and try again."
msgstr "¡Por favor revise sus parámetros y vuelva a intentarlo!"
msgstr "Por favor, revisa tus parámetros y vuelve a intentarlo."
msgid "No satellites.xml file is selected!"
msgstr "¡Ningún satellites.xml seleccionado!"
msgid "Error. Verify the data!"
msgstr "Error. ¡Revise los datos!"
msgstr "Error. ¡Revisa los datos!"
msgid "Operation not allowed in this context!"
msgstr "¡Operación no permitida en este contexto!"
msgid "No VLC is found. Check that it is installed!"
msgstr "VLC no encontrado. ¡Compruebe que está instalado!"
msgstr "VLC no encontrado. ¡Comprueba que está instalado!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Por favor espere, hay una prueba de stream en progreso..."
msgstr "Por favor espera, hay una prueba de stream en progreso..."
msgid "Found"
msgstr "Encontrado"
msgstr "Encontrado(s)"
msgid "unavailable streams."
msgstr "Streams no presentes."
msgstr "streams no presentes."
msgid "No changes required!"
msgstr "¡No se requieren cambios!"
@@ -657,10 +670,10 @@ msgid "No bouquet file is selected!"
msgstr "¡No se ha seleccionado nigún fichero de bouquet!"
msgid "Remove all unused"
msgstr "Quitar todos sin usar"
msgstr "Borrar los que están sin usar"
msgid "Test"
msgstr "Prueba"
msgstr "Probar"
msgid "Test connection"
msgstr "Probar conexión"
@@ -684,7 +697,7 @@ msgid "Enable HTTP API"
msgstr "Habilitar API HTTP"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Poner el canal (Ctrl + Z)"
msgstr "Poner (zapear) el canal (Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Poner el canal y ver en el programa (Ctrl + W)"
@@ -708,7 +721,7 @@ msgid "Service names source:"
msgstr "Origen nombres de servicio:"
msgid "Main service list"
msgstr "Lista principal de servicios:"
msgstr "Lista principal de servicios"
msgid "XML file"
msgstr "Fichero XML"
@@ -770,8 +783,12 @@ msgstr "Terminar reproducción"
msgid "Import YouTube playlist"
msgstr "Importar lista de reproducción de YouTube"
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
msgstr "¡Se ha encontrado enlace al recurso de YouTube!\n¿Intentar obtener un enlace directo al vídeo?"
msgid ""
"Found a link to the YouTube resource!\n"
"Try to get a direct link to the video?"
msgstr ""
"¡Se ha encontrado un enlace al recurso de YouTube!\n"
"¿Quieres intentar obtener un enlace directo al vídeo?"
msgid "Playlist import"
msgstr "Importar lista de reproducción"
@@ -786,7 +803,7 @@ msgid "Apply profile settings"
msgstr "Aplicar ajustes de perfil"
msgid "Settings type:"
msgstr "Tipo de ajustes:"
msgstr "Tipo:"
msgid "Set default"
msgstr "Por defecto"
@@ -813,7 +830,7 @@ msgid "Drag or paste the link here"
msgstr "Soltar o pegar en enlace aquí"
msgid "Remove added links in the playlist"
msgstr "Quitar los enlaces añadidos en la lista de reproducción"
msgstr "Borrar los enlaces añadidos a la lista de reproducción"
msgid "A bouquet with that name exists!"
msgstr "¡Ya existe un bouquet con ese nombre!"
@@ -867,7 +884,7 @@ msgid "IPTV tools"
msgstr "Intrumentos IPTV"
msgid "Make profile folder as default for the additional data"
msgstr "Usar por defecto el directorio de perfil para los datos adionales"
msgstr "Usar por defecto el directorio de perfil para los datos adicionales"
msgid "Default data path:"
msgstr "Ruta estándar de datos:"
@@ -923,8 +940,8 @@ msgstr "Modo de reproducción de los streams:"
msgid "Built-in player"
msgstr "Reproductor interno"
msgid "VLC media player"
msgstr "Reproductor VLC"
msgid "In a separate window"
msgstr "En una ventana aparte"
msgid "Only get m3u file"
msgstr "Sólo bajar fichero m3u"
@@ -933,7 +950,7 @@ msgid "Save and restart the program to apply the settings."
msgstr "Guardar y reiniciar el programa para aplicar la configuración."
msgid "Some images may have problems displaying the favorites list!"
msgstr "Algunas imágenes pueden tener problemas al mostrar la lista de favoritos!"
msgstr "¡Algunas imágenes pueden tener problemas al mostrar la lista de favoritos!"
msgid "Operates in standby mode or current active transponder!"
msgstr "¡Funciona en modo de espera o transpondedor activo actual!"
@@ -984,7 +1001,7 @@ msgid "Download from the receiver"
msgstr "Descargar desde el receptor"
msgid "Remove all picons from the receiver"
msgstr "Eliminar todos los picons del receptor"
msgstr "Borrar todos los picons del receptor"
msgid "Service reference"
msgstr "Referencia del servicio"
@@ -1010,14 +1027,24 @@ msgstr "¡EXPERIMENTAL!"
msgid "Sorting data..."
msgstr "Ordenando datos..."
msgid "There are unsaved changes.\n\n\t Save them now?"
msgstr "Hay cambios sin guardar.\n\n\t ¿Desea guardarlos ahora?"
msgid ""
"There are unsaved changes.\n"
"\n"
"\t Save them now?"
msgstr ""
"Hay cambios sin guardar.\n"
"\n"
"\t ¿Desea guardarlos ahora?"
msgid "Are you sure you want to change the order\n\t of services in this bouquet?"
msgstr "¿Está seguro de querer cambiar el orden\n\t de servicios en este bouquet?"
msgid ""
"Are you sure you want to change the order\n"
"\t of services in this bouquet?"
msgstr ""
"¿Está seguro de querer cambiar el orden\n"
"\t de servicios en este bouquet?"
msgid "Remove from the receiver"
msgstr "Eliminar del receptor"
msgstr "Borrar del receptor"
msgid "Screenshot"
msgstr "Captura de pantalla"
@@ -1025,3 +1052,333 @@ msgstr "Captura de pantalla"
msgid "Video"
msgstr "Vídео"
msgid "The Neutrino has only experimental support. Not all features are supported!"
msgstr "El Neutrino sólo dispone de soporte experimental. ¡No todas las funciones estarán disponibles!"
msgid "Enable experimental features"
msgstr "Activar las funciones experimentales"
msgid "Can't Playback!"
msgstr "¡No se puede reproducir!"
msgid "Enable Dark Mode"
msgstr "Habilitar modo oscuro"
msgid "Extract..."
msgstr "Extraer..."
msgid "Unsupported format!"
msgstr "¡Formato no soportado!"
msgid "Combine with the current data?"
msgstr "¿Combinar con los datos actuales?"
msgid "Importing data done!"
msgstr "¡Importación finalizada!"
msgid "Current service"
msgstr "Servicio actual"
msgid "Open folder"
msgstr "Abrir carpeta"
msgid "Open archive"
msgstr "Abrir fichero"
msgid "Import from Web"
msgstr "Importar de Internet"
msgid "Control"
msgstr "Control"
msgid "Timers"
msgstr "Temporizadores"
msgid "Timer"
msgstr "Temporizador"
msgid "Add timer"
msgstr "Añadir temporizador"
msgid "Hr."
msgstr "Hora"
msgid "Min."
msgstr "Min."
msgid "Power"
msgstr "Power"
msgid "Standby"
msgstr "Standby"
msgid "Wake Up"
msgstr "Despertar"
msgid "Reboot"
msgstr "Reiniciar"
msgid "Restart GUI"
msgstr "Reiniciar GUI"
msgid "Shutdown"
msgstr "Apagar"
msgid "Shut down"
msgstr "Apagar"
msgid "Do Nothing"
msgstr "No hacer nada"
msgid "Auto"
msgstr "Auto"
msgid "Grab screenshot"
msgstr "Captura de pantalla"
msgid "Enabled:"
msgstr "Activo:"
msgid "Name:"
msgstr "Nombre:"
msgid "Description:"
msgstr "Descripción:"
msgid "Service:"
msgstr "Servicio:"
msgid "Service reference:"
msgstr "Referencia del servicio:"
msgid "Event ID:"
msgstr "ID evento:"
msgid "Begins:"
msgstr "Inicio:"
msgid "Ends:"
msgstr "Final:"
msgid "Repeated:"
msgstr "Repetir:"
msgid "Action:"
msgstr "Acción:"
msgid "After event:"
msgstr "Después del evento:"
msgid "Location:"
msgstr "Ubicación:"
msgid "Mo"
msgstr "Lu"
msgid "Tu"
msgstr "Ma"
msgid "We"
msgstr "Mi"
msgid "Th"
msgstr "Ju"
msgid "Fr"
msgstr "Vi"
msgid "Sa"
msgstr "Sa"
msgid "Su"
msgstr "Do"
msgid "Set"
msgstr "Poner"
msgid "Services update"
msgstr "Actualización servicios"
msgid "Create folder"
msgstr "Crear carpeta"
msgid "FTP client"
msgstr "Cliente FTP"
msgid "The file size is too large!"
msgstr "¡El fichero es demasiado grande!"
msgid "Connect"
msgstr "Conectar"
msgid "Disconnect"
msgstr "Desconectar"
msgid "Size"
msgstr "Tamaño"
msgid "Date"
msgstr "Fecha"
msgid "Attr."
msgstr "Atr."
msgid "Toggle display position"
msgstr "Cambiar la posición de la pantalla"
msgid "Alternatives"
msgstr "Alternativas"
msgid "Add alternatives"
msgstr "Añadir alternativas"
msgid "DreamOS only!"
msgstr "¡Sólo DreamOS!"
msgid "A similar service is already in this list!"
msgstr "¡Un servicio similar ya está en esta lista!"
msgid ""
"Play mode has been changed!\n"
"Restart the program to apply the settings."
msgstr ""
"¡El modo de reproducción se ha cambiado!\n"
"Reinicia el programa para aplicar los cambios."
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr "¡Poner los valores de TID, NID y Namespace para una correcta nomenclatura de los picons!"
msgid "Streams detected:"
msgstr "Streams encontrados:"
msgid "Download picons"
msgstr "Descargar picons"
msgid "Errors:"
msgstr "Errores:"
msgid "Use to play streams:"
msgstr "Reproductor de streams:"
msgid "Font in the lists:"
msgstr "Fuente en las listas:"
msgid "Picons size in the lists:"
msgstr "Tamaño de los picons en las listas:"
msgid "Logo size in tooltips:"
msgstr "Tamaño de los logos en las listas:"
msgid "Save as"
msgstr "Guardar como"
msgid "Mark duplicates"
msgstr "Marcar duplicados"
msgid "Load only for selected bouquet"
msgstr "Cargar sólo para el bouquet seleccionado"
msgid "The task is canceled!"
msgstr "¡Se ha cancelado la tarea!"
msgid "Data loading in progress!"
msgstr "¡Carga de datos en progreso!"
msgid "Recordings"
msgstr "Grabaciones"
msgid "Help"
msgstr "Ayuda"
msgid "HTTP API is not activated. Check your settings!"
msgstr "HTTP API no activa. ¡Comprueba la configuración!"
msgid "Add picons"
msgstr "Añadir picons"
msgid "Logs"
msgstr "Logs"
msgid "Title"
msgstr "Título"
msgid "Time"
msgstr "Hora"
msgid "Length"
msgstr "Duración"
msgid "Additional source"
msgstr "Fuente adicional"
msgid "Automatically set the name selected in the favorites list."
msgstr "Poner automáticamente el nombre en la lista de favoritos seleccionada."
msgid "Playback"
msgstr "Reproducción"
msgid "Audio"
msgstr "Audio"
msgid "Audio Track"
msgstr "Pista de audio"
msgid "Subtitle"
msgstr "Subtítulos"
msgid "Subtitle Track"
msgstr "Pista de subtítulos"
msgid "Aspect ratio"
msgstr "Relación de aspecto"
msgid "This may change the settings of other profiles!"
msgstr "¡Esto podría cambiar la configuración de otros perfiles!"
msgid "Drag the services to the desired picon or picon to the list of selected services."
msgstr "Arrastrar los servicios al picon deseado o al picon de la lista de los servicios seleccionados."
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Pone la carpeta del perfil como por defecto para almacenar picons, backups, etc."
msgid "New sub-bouquet"
msgstr "Nuevo sub-bouquet"
msgid "Mark not presented in Bouquets"
msgstr "Marcar los no presentes en los bouquets"
msgid "Not in Bouquets"
msgstr "No en los bouquets"
msgid "Do not show services present in Bouquets."
msgstr "No mostrar los servicios presentes en los bouquets."
msgid "IPTV services only"
msgstr "Sólo servicios IPTV"
msgid "Display picons"
msgstr "Mostrar picons"
msgid "Alternate layout"
msgstr "Diseño alternativo"
msgid "Layout of elements has been changed!"
msgstr "¡El diseño de los elementos ha cambiado!"
msgid "Restart the program to apply all changes."
msgstr "Reinicia el programa para aplicar los cambios."
msgid "New folder"
msgstr "Nueva carpeta"
msgid "Rename"
msgstr "Renombrar"
msgid "Bookmarks"
msgstr "Marcadores"
msgid "Add bookmark"
msgstr "Añadir marcador"
msgid "All bouquets"
msgstr "Todos los bouquets"

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 Dmitriy Yefremov
# Copyright (C) 2018-2022 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -1280,6 +1280,9 @@ msgstr "Автоматическая установка имени из спис
msgid "Playback"
msgstr "Воспроизведение"
msgid "Playback:"
msgstr "Воспроизведение:"
msgid "Audio"
msgstr "Аудио"
@@ -1330,3 +1333,21 @@ msgstr "Изменено расположение элементов!"
msgid "Restart the program to apply all changes."
msgstr "Перезапустите программу, чтобы применить все изменения."
msgid "New folder"
msgstr "Создать папку"
msgid "Rename"
msgstr "Переименовать"
msgid "Bookmarks"
msgstr "Закладки"
msgid "Add bookmark"
msgstr "Добавить в закладки"
msgid "All bouquets"
msgstr "Все букеты"
msgid "Playback from the main list"
msgstr "Воспроизведение из основного списка"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: DemonEditor\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
"PO-Revision-Date: 2021-11-11 23:49+0300\n"
"PO-Revision-Date: 2022-02-21 22:06+0300\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -53,6 +53,9 @@ msgstr "Geçerli IP:"
msgid "Assign"
msgstr "Ata"
msgid "Assign file"
msgstr "Dosya ata"
msgid "Bouquet details"
msgstr "Buket detayları"
@@ -233,6 +236,9 @@ msgstr "Profili sıfırla"
msgid "Satellites"
msgstr "Uydular"
msgid "Transponders"
msgstr "Transponders"
msgid "Satellites.xml file:"
msgstr "Satellites.xml dosyası:"
@@ -1329,3 +1335,30 @@ msgstr "Hizmetleri istediğiniz simgeye veya simgeyi seçili hizmetler listesine
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Picon'ları, yedekleri vb. depolamak için profil klasörünü varsayılan olarak ayarlar."
msgid "New sub-bouquet"
msgstr "Yeni alt buket"
msgid "Mark not presented in Bouquets"
msgstr "Buketlerde olmayanları işaretleyin"
msgid "Not in Bouquets"
msgstr "Buketlerde Değil"
msgid "Do not show services present in Bouquets."
msgstr "Buketlerde bulunan hizmetleri göstermeyin."
msgid "IPTV services only"
msgstr "Yalnızca IPTV hizmetleri"
msgid "Display picons"
msgstr "Display piconlar"
msgid "Alternate layout"
msgstr "Alternatif düzen"
msgid "Layout of elements has been changed!"
msgstr "Öğelerin düzeni değiştirildi!"
msgid "Restart the program to apply all changes."
msgstr "Tüm değişiklikleri uygulamak için programı yeniden başlatın."