Compare commits

...

17 Commits

Author SHA1 Message Date
DYefremov
1c26887e8f README update 2021-03-26 21:19:50 +03:00
DYefremov
2d67fbfe44 Russian, Belarusian and German translations update 2021-03-26 15:11:28 +03:00
DYefremov
f502bce2aa added multiple choice of pos and type to filter feature 2021-03-26 11:46:43 +03:00
DYefremov
8c442280ec added base support for mpv 2021-03-23 22:13:33 +03:00
DYefremov
3b79677a9b some corrections for playback mode 2021-03-17 09:51:02 +03:00
DYefremov
33529ca010 minor fix for epg 2021-03-14 21:33:46 +03:00
DYefremov
eae14a472f added logo for the tooltip in the picon explorer 2021-03-14 13:17:48 +03:00
DYefremov
f85bbceceb added recommends to *.deb 2021-03-13 10:17:54 +03:00
DYefremov
f7cfc5a9a0 fixed picons download for providers 2021-03-12 13:37:06 +03:00
DYefremov
6b57c8f617 added display bouquet list in playback mode 2021-03-12 10:30:12 +03:00
DYefremov
1006251490 added new appearance options [list font, picons size] 2021-03-12 10:24:09 +03:00
DYefremov
e871492006 added dependencies to *.deb 2021-03-09 18:17:10 +03:00
DYefremov
353fb9cc53 reworking of built-in player [GStreamer support] 2021-03-09 18:14:15 +03:00
DYefremov
497964822e bump version 2021-02-23 10:32:54 +03:00
DYefremov
a067821d46 copy tr *.mo file 2021-02-23 10:27:06 +03:00
audi06_19
ab52964a20 Turkish translations update (#45) 2021-02-23 10:14:58 +03:00
DYefremov
344ac43996 improved satellites import [added KingOfSat support] 2021-02-20 15:29:49 +03:00
33 changed files with 3689 additions and 701 deletions

View File

@@ -22,7 +22,7 @@ Focused on the convenience of working in lists from the keyboard. The mouse is a
* Import to bouquet(Neutrino WEBTV) from m3u.
* Export of bouquets with IPTV services in m3u.
* Assignment of EPG from DVB or XML for IPTV services (only Enigma2, experimental).
* Preview (playback) of IPTV or other streams directly from the bouquet list (should be installed [VLC](https://www.videolan.org/vlc/)).
* Preview (playback) of IPTV or other streams directly from the bouquet list.
[<img src="https://user-images.githubusercontent.com/7511379/103151911-89a52c00-4793-11eb-9941-8430f4e87eef.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103151911-89a52c00-4793-11eb-9941-8430f4e87eef.png)
* Control panel with the ability to view EPG and manage timers (via HTTP API, experimental).
[<img src="https://user-images.githubusercontent.com/7511379/103150898-c79d5280-4789-11eb-9d16-e7f89225738b.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150898-c79d5280-4789-11eb-9d16-e7f89225738b.png)
@@ -57,9 +57,9 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
## Minimum requirements
*Python >= 3.5.2, GTK+ >= 3.16 with PyGObject bindings, python3-requests.*
***Optional:** python3-gi-cairo, python3-pil, python3-chardet.*
*Python >= 3.5.2, GTK+ >= 3.22, python3-gi, python3-gi-cairo, python3-requests.*
***Optional:** python3-pil, python3-chardet.*
## Installation and Launch
* ### Linux
To start the program, in most cases it is enough to download the [archive](https://github.com/DYefremov/DemonEditor/archive/master.zip), unpack
@@ -86,6 +86,8 @@ When using the multiple import feature, from *lamedb* will be taken data **only
If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.
For streams playback, this app supports [VLC](https://www.videolan.org/vlc/), [MPV](https://mpv.io/) and [GStreamer](https://gstreamer.freedesktop.org/). Depending on your distro, you may need to install additional packages and libraries.
#### Command line arguments:
* **-l** - write logs to file.
* **-d on/off** - turn on/off debug mode. Allows to display more information in the logs.

View File

@@ -53,9 +53,9 @@ def write_satellites(satellites, data_path):
transponder_child.setAttribute("frequency", tr.frequency)
transponder_child.setAttribute("symbol_rate", tr.symbol_rate)
transponder_child.setAttribute("polarization", get_key_by_value(POLARIZATION, tr.polarization))
transponder_child.setAttribute("fec_inner", get_key_by_value(FEC, tr.fec_inner))
transponder_child.setAttribute("system", get_key_by_value(SYSTEM, tr.system))
transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation))
transponder_child.setAttribute("fec_inner", get_key_by_value(FEC, tr.fec_inner) or "0")
transponder_child.setAttribute("system", get_key_by_value(SYSTEM, tr.system) or "0")
transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation) or "0")
if tr.pls_mode:
transponder_child.setAttribute("pls_mode", tr.pls_mode)
if tr.pls_code:
@@ -90,7 +90,6 @@ def parse_transponders(elem, sat_name):
atr["is_id"].value if "is_id" in atr else None)
except Exception as e:
message = "Error: can't parse transponder for '{}' satellite! {}".format(sat_name, repr(e))
print(message)
log(message)
else:
transponders.append(tr)

View File

@@ -30,8 +30,11 @@ class Defaults(Enum):
USE_COLORS = True
NEW_COLOR = "rgb(255,230,204)"
EXTRA_COLOR = "rgb(179,230,204)"
TOOLTIP_LOGO_SIZE = 96
LIST_PICON_SIZE = 32
FAV_CLICK_MODE = 0
PLAY_STREAMS_MODE = 1 if IS_DARWIN else 0
STREAM_LIB = "vlc"
PROFILE_FOLDER_DEFAULT = False
RECORDS_PATH = DATA_PATH + "records/"
ACTIVATE_TRANSCODING = False
@@ -436,6 +439,14 @@ class Settings:
def play_streams_mode(self, value):
self._settings["play_streams_mode"] = value
@property
def stream_lib(self):
return self._settings.get("stream_lib", Defaults.STREAM_LIB.value)
@stream_lib.setter
def stream_lib(self, value):
self._settings["stream_lib"] = value
# *********** EPG ************ #
@property
@@ -513,30 +524,6 @@ class Settings:
def enable_send_to(self, value):
self._settings["enable_send_to"] = value
@property
def use_colors(self):
return self._settings.get("use_colors", Defaults.USE_COLORS.value)
@use_colors.setter
def use_colors(self, value):
self._settings["use_colors"] = value
@property
def new_color(self):
return self._settings.get("new_color", Defaults.NEW_COLOR.value)
@new_color.setter
def new_color(self, value):
self._settings["new_color"] = value
@property
def extra_color(self):
return self._settings.get("extra_color", Defaults.EXTRA_COLOR.value)
@extra_color.setter
def extra_color(self, value):
self._settings["extra_color"] = value
@property
def fav_click_mode(self):
return self._settings.get("fav_click_mode", Defaults.FAV_CLICK_MODE.value)
@@ -581,6 +568,54 @@ class Settings:
# *********** Appearance *********** #
@property
def list_font(self):
return self._settings.get("list_font", "")
@list_font.setter
def list_font(self, value):
self._settings["list_font"] = value
@property
def list_picon_size(self):
return self._settings.get("list_picon_size", Defaults.LIST_PICON_SIZE.value)
@list_picon_size.setter
def list_picon_size(self, value):
self._settings["list_picon_size"] = value
@property
def tooltip_logo_size(self):
return self._settings.get("tooltip_logo_size", Defaults.TOOLTIP_LOGO_SIZE.value)
@tooltip_logo_size.setter
def tooltip_logo_size(self, value):
self._settings["tooltip_logo_size"] = value
@property
def use_colors(self):
return self._settings.get("use_colors", Defaults.USE_COLORS.value)
@use_colors.setter
def use_colors(self, value):
self._settings["use_colors"] = value
@property
def new_color(self):
return self._settings.get("new_color", Defaults.NEW_COLOR.value)
@new_color.setter
def new_color(self, value):
self._settings["new_color"] = value
@property
def extra_color(self):
return self._settings.get("extra_color", Defaults.EXTRA_COLOR.value)
@extra_color.setter
def extra_color(self, value):
self._settings["extra_color"] = value
@property
def dark_mode(self):
return self._settings.get("dark_mode", False)

View File

@@ -1,32 +1,339 @@
import os
import sys
from abc import ABC, abstractmethod
from datetime import datetime
from app.commons import run_task, log, _DATE_FORMAT
class Player:
""" Simple wrapper for VLC media player. """
__VLC_INSTANCE = None
class Player(ABC):
""" Base player class. Also used as a factory. """
def __init__(self, mode, rewind_cb, position_cb, error_cb, playing_cb):
@abstractmethod
def get_play_mode(self):
pass
@abstractmethod
def play(self, mrl=None):
pass
@abstractmethod
def stop(self):
pass
@abstractmethod
def pause(self):
pass
@abstractmethod
def set_time(self, time):
pass
@abstractmethod
def release(self):
pass
@abstractmethod
def is_playing(self):
pass
@abstractmethod
def get_instance(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
pass
def get_window_handle(self, widget):
""" Returns the identifier [pointer] for the window.
Based on gtkvlc.py[get_window_pointer] example from here:
https://github.com/oaubert/python-vlc/tree/master/examples
"""
if sys.platform == "linux":
return widget.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")
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, 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(widget.get_window().__gpointer__, None)
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]
return get_pointer(gpointer)
def get_video_widget(self, widget):
from gi.repository import Gtk, Gdk
area = Gtk.DrawingArea(visible=True)
area.connect("draw", self.on_drawing_area_draw)
area.set_events(Gdk.ModifierType.BUTTON1_MASK)
widget.add(area)
return area
def on_drawing_area_draw(self, widget, cr):
""" Used for black background drawing in the player drawing area. """
cr.set_source_rgb(0, 0, 0)
cr.paint()
@staticmethod
def make(name, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
""" Factory method. We will not use a separate factory to return a specific implementation.
@param name: implementation name.
@param mode: current player mode [Built-in or windowed].
@param widget: parent of video widget.
@param buf_cb: buffering callback.
@param position_cb: time (position) callback.
@param error_cb: error callback.
@param playing_cb: playing state callback.
Throws a NameError if there is no implementation for the given name.
"""
if name == "mpv":
return MpvPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
elif name == "gst":
return GstPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
elif name == "vlc":
return VlcPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
else:
raise NameError("There is no such [{}] implementation.".format(name))
class MpvPlayer(Player):
""" Simple wrapper for MPV media player.
Uses python-mvp [https://github.com/jaseg/python-mpv].
"""
__INSTANCE = None
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
try:
from app.tools import vlc
from app.tools.vlc import EventType
from app.tools import mpv
self._player = mpv.MPV(wid=str(self.get_window_handle(self.get_video_widget(widget))))
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError
raise ImportError("No libmpv is found. Check that it is installed!")
else:
self._mode = mode
self._is_playing = False
@self._player.event_callback(mpv.MpvEventID.FILE_LOADED)
def on_open(event):
log("Starting playback...")
playing_cb()
@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)))
error_cb()
@classmethod
def get_instance(cls, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
if not cls.__INSTANCE:
cls.__INSTANCE = MpvPlayer(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
return cls.__INSTANCE
def get_play_mode(self):
return self._mode
@run_task
def play(self, mrl=None):
if not mrl:
return
self._player.play(mrl)
self._is_playing = True
@run_task
def stop(self):
self._player.stop()
self._is_playing = True
def pause(self):
pass
def set_time(self, time):
pass
@run_task
def release(self):
self._player.terminate()
self.__INSTANCE = None
def is_playing(self):
return self._is_playing
class GstPlayer(Player):
""" Simple wrapper for GStreamer playbin. """
__INSTANCE = None
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
try:
import gi
gi.require_version("Gst", "1.0")
gi.require_version("GstVideo", "1.0")
from gi.repository import Gst, GstVideo
# Initialization of GStreamer.
Gst.init(sys.argv)
gtk_sink = Gst.ElementFactory.make("gtksink")
if not gtk_sink:
msg = "GStreamer error: gtksink plugin not installed!"
log(msg)
raise ImportError(msg)
except (OSError, ValueError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No GStreamer is found. Check that it is installed!")
else:
self._error_cb = error_cb
self._playing_cb = playing_cb
self.STATE = Gst.State
self.STAT_RETURN = Gst.StateChangeReturn
self._mode = mode
self._is_playing = False
self._player = Gst.ElementFactory.make("playbin", "player")
# Initialization of the playback widget.
self._player.set_property("video-sink", gtk_sink)
vid_widget = gtk_sink.props.widget
widget.add(vid_widget)
vid_widget.show()
bus = self._player.get_bus()
bus.add_signal_watch()
bus.connect("message::error", self.on_error)
bus.connect("message::state-changed", self.on_state_changed)
bus.connect("message::eos", self.on_eos)
@classmethod
def get_instance(cls, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
if not cls.__INSTANCE:
cls.__INSTANCE = GstPlayer(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
return cls.__INSTANCE
def get_play_mode(self):
return self._mode
def play(self, mrl=None):
self._player.set_state(self.STATE.READY)
if not mrl:
return
self._player.set_property("uri", mrl)
log("Setting the URL for playback: {}".format(mrl))
ret = self._player.set_state(self.STATE.PLAYING)
if ret == self.STAT_RETURN.FAILURE:
log("ERROR: Unable to set the 'PLAYING' state for '{}'.".format(mrl))
else:
self._is_playing = True
def stop(self):
log("Stop playback...")
self._player.set_state(self.STATE.READY)
self._is_playing = False
def pause(self):
self._player.set_state(self.STATE.PAUSED)
def set_time(self, time):
pass
@run_task
def release(self):
self._is_playing = False
self._player.set_state(self.STATE.NULL)
self.__INSTANCE = None
def set_mrl(self, mrl):
self._player.set_property("uri", mrl)
def is_playing(self):
return self._is_playing
def on_error(self, bus, msg):
err, dbg = msg.parse_error()
log(err)
self._error_cb()
def on_state_changed(self, bus, msg):
if not msg.src == self._player:
# Not from the player.
return
old_state, new_state, pending = msg.parse_state_changed()
if new_state is self.STATE.PLAYING:
log("Starting playback...")
self._playing_cb()
self.get_stream_info()
def on_eos(self, bus, msg):
""" Called when an end-of-stream message appears. """
self._player.set_state(self.STATE.READY)
self._is_playing = False
def get_stream_info(self):
log("Getting stream info...")
nr_video = self._player.get_property("n-video")
for i in range(nr_video):
# Retrieve the stream's video tags.
tags = self._player.emit("get-video-tags", i)
if tags:
_, cod = tags.get_string("video-codec")
log("Video codec: {}".format(cod or "unknown"))
nr_audio = self._player.get_property("n-audio")
for i in range(nr_audio):
# Retrieve the stream's video tags.
tags = self._player.emit("get-audio-tags", i)
if tags:
_, cod = tags.get_string("audio-codec")
log("Audio codec: {}".format(cod or "unknown"))
class VlcPlayer(Player):
""" Simple wrapper for VLC media player.
Uses python-vlc [https://github.com/oaubert/python-vlc].
"""
__VLC_INSTANCE = None
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
try:
from app.tools import vlc
from app.tools.vlc import EventType
args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
self._player = vlc.Instance(args).media_player_new()
except (OSError, AttributeError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No VLC is found. Check that it is installed!")
else:
self._mode = mode
self._is_playing = False
ev_mgr = self._player.event_manager()
if rewind_cb:
if buf_cb:
# TODO look other EventType options
ev_mgr.event_attach(EventType.MediaPlayerBuffering,
lambda et, p: rewind_cb(p.get_media().get_duration()),
lambda et, p: buf_cb(p.get_media().get_duration()),
self._player)
if position_cb:
ev_mgr.event_attach(EventType.MediaPlayerTimeChanged,
@@ -42,10 +349,12 @@ class Player:
lambda et, p: playing_cb(),
self._player)
self.init_video_widget(widget)
@classmethod
def get_instance(cls, mode, rewind_cb=None, position_cb=None, error_cb=None, playing_cb=None):
def get_instance(cls, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
if not cls.__VLC_INSTANCE:
cls.__VLC_INSTANCE = Player(mode, rewind_cb, position_cb, error_cb, playing_cb)
cls.__VLC_INSTANCE = VlcPlayer(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
return cls.__VLC_INSTANCE
def get_play_mode(self):
@@ -78,37 +387,20 @@ class Player:
self._player.release()
self.__VLC_INSTANCE = None
def set_xwindow(self, xid):
self._player.set_xwindow(xid)
def set_nso(self, widget):
""" Used on MacOS to set NSObject.
Based on gtkvlc.py[get_window_pointer] example from here:
https://github.com/oaubert/python-vlc/tree/master/examples
"""
try:
import ctypes
g_dll = ctypes.CDLL("libgdk-3.0.dylib")
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
else:
get_nsview = g_dll.gdk_quartz_window_get_nsview
get_nsview.restype, get_nsview.argtypes = ctypes.c_void_p, [ctypes.c_void_p]
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
# Get the C void* pointer to the window
pointer = ctypes.pythonapi.PyCapsule_GetPointer(widget.get_window().__gpointer__, None)
self._player.set_nsobject(get_nsview(pointer))
def set_mrl(self, mrl):
self._player.set_mrl(mrl)
def is_playing(self):
return self._is_playing
def set_full_screen(self, full):
self._player.set_fullscreen(full)
def init_video_widget(self, widget):
video_widget = self.get_video_widget(widget)
if sys.platform == "linux":
self._player.set_xwindow(video_widget.get_window().get_xid())
elif sys.platform == "darwin":
self._player.set_nsobject(self.get_window_handle(video_widget))
else:
log("Video widget initialization error: platform '{}' is not supported. ".format(sys.platform))
class Recorder:

1941
app/tools/mpv.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ _ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
_NEUTRINO_PICON_KEY = "{:x}{:04x}{:04x}.png"
Provider = namedtuple("Provider", ["logo", "name", "pos", "url", "on_id", "ssid", "single", "selected"])
Picon = namedtuple("Picon", ["ref", "ssid", "v_pid"])
Picon = namedtuple("Picon", ["ref", "ssid"])
class PiconsParser(HTMLParser):
@@ -63,18 +63,17 @@ class PiconsParser(HTMLParser):
ln = len(row)
if self._single and ln == 4 and row[0].startswith("/logo/"):
self.picons.append(Picon(row[0].strip(), "0", "0"))
self.picons.append(Picon(row[0].strip(), "0"))
else:
if 9 < ln < 13:
if ln == 9:
url = None
if row[0].startswith("/logo/"):
url = row[0]
elif row[1].startswith("/logo/"):
url = row[1]
ssid = row[-4]
if url and len(ssid) > 2:
self.picons.append(Picon(url, ssid, row[-3]))
if url and row[-3].isdigit():
self.picons.append(Picon(url, row[-3]))
self._current_row = []

View File

@@ -14,13 +14,14 @@ from app.eparser import Satellite, Transponder, is_transponder_valid
from app.eparser.ecommons import (PLS_MODE, get_key_by_value, FEC, SYSTEM, POLARIZATION, MODULATION, SERVICE_TYPE,
Service, CAS)
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0"}
_HEADERS = {"User-Agent": "Mozilla/5.0 (Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0"}
class SatelliteSource(Enum):
FLYSAT = ("https://www.flysat.com/satlist.php",)
LYNGSAT = ("https://www.lyngsat.com/asia.html", "https://www.lyngsat.com/europe.html",
"https://www.lyngsat.com/atlantic.html", "https://www.lyngsat.com/america.html")
KINGOFSAT = ("https://en.kingofsat.net/satellites.php",)
@staticmethod
def get_sources(src):
@@ -96,7 +97,9 @@ class SatellitesParser(HTMLParser):
if tag == "tr":
self._is_th = True
if tag == "a":
self._current_row.append(attrs[0][1])
for atr in attrs:
if atr[0] == "href":
self._current_row.append(atr[1])
def handle_data(self, data):
""" Save content to a cell """
@@ -182,6 +185,11 @@ class SatellitesParser(HTMLParser):
elif r_len == 5:
sats.append((row[2], current_pos, row[3], base_url + row[1], False))
return sats
elif source is SatelliteSource.KINGOFSAT:
def get_sat(r):
return r[3], self.parse_position(r[1]), None, r[0], False
return list(map(get_sat, filter(lambda x: len(x) == 17, self._rows)))
def get_satellite(self, sat):
pos = sat[1]
@@ -201,18 +209,29 @@ class SatellitesParser(HTMLParser):
def get_transponders(self, sat_url):
""" Getting transponders(sorted by frequency). """
self._rows.clear()
url = "https://www.flysat.com/" + sat_url if self._source is SatelliteSource.FLYSAT else sat_url
request = requests.get(url=url, headers=_HEADERS)
trs = []
if request.status_code == 200:
self.feed(request.text)
if self._source is SatelliteSource.FLYSAT:
self.get_transponders_for_fly_sat(trs)
elif self._source is SatelliteSource.LYNGSAT:
self.get_transponders_for_lyng_sat(trs)
url = sat_url
if self._source is SatelliteSource.FLYSAT:
url = "https://www.flysat.com/" + sat_url
elif self._source is SatelliteSource.KINGOFSAT:
url = "https://en.kingofsat.net/" + sat_url
try:
request = requests.get(url=url, headers=_HEADERS)
except requests.exceptions.ConnectionError as e:
log("Getting transponders error: {}".format(e))
else:
log("SatellitesParser [get transponders] error: {} {}".format(url, request.reason))
if request.status_code == 200:
self.feed(request.text)
if self._source is SatelliteSource.FLYSAT:
self.get_transponders_for_fly_sat(trs)
elif self._source is SatelliteSource.LYNGSAT:
self.get_transponders_for_lyng_sat(trs)
elif self._source is SatelliteSource.KINGOFSAT:
self.get_transponders_for_king_of_sat(trs)
else:
log("SatellitesParser [get transponders] error: {} {}".format(url, request.reason))
return sorted(trs, key=lambda x: int(x.frequency))
@@ -296,6 +315,26 @@ class SatellitesParser(HTMLParser):
if is_transponder_valid(tr):
trs.append(tr)
def get_transponders_for_king_of_sat(self, trs):
""" Getting transponders for KingOfSat source.
Since the *.ini file contains incomplete information, it is not used.
"""
zeros = "000"
pos_pat = re.compile(r".*?(\d+\.\d°[EW]).*")
pat = re.compile(
r"(\d+).00\s+([RLHV])\s+(DVB-S[2]?)\s+(?:T2-MI, PLP (\d+)\s+)?(.*PSK).*?(?:Stream\s+(\d+))?\s+(\d+)\s+(\d+/\d+)$")
for row in filter(lambda r: len(r) == 16 and pos_pat.match(r[0]), self._rows):
res = pat.search(" ".join((row[0], row[2], row[3], row[8], row[9], row[10])))
if res:
freq, sr, pol, fec, sys = res.group(1), res.group(7), res.group(2), res.group(8), res.group(3)
mod, pls_id, pls_code = res.group(5), res.group(4), res.group(6)
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, None, pls_code, pls_id)
if is_transponder_valid(tr):
trs.append(tr)
class ServicesParser(HTMLParser):
""" Services parser for LYNGSAT source. """
@@ -470,27 +509,8 @@ class ServicesParser(HTMLParser):
else:
flags = ",".join(filter(None, (flags, cas)))
srv = Service(flags_cas=flags,
transponder_type="s",
coded=None,
service=name,
locked=None,
hide=None,
package=pkg,
service_type=_s_type,
picon=r[1].img,
picon_id=picon_id,
ssid=sid,
freq=freq,
rate=sr,
pol=pol,
fec=fec,
system=sys,
pos=pos,
data_id=data_id,
fav_id=fav_id,
transponder=tr)
services.append(srv)
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, r[1].img, picon_id,
sid, freq, sr, pol, fec, sys, pos, data_id, fav_id, tr))
except ValueError as e:
log("ServicesParser error [get transponder services]: {}".format(e))

View File

@@ -61,19 +61,19 @@ class ControlBox(Gtk.HBox):
@property
def event_data(self):
return self._event_data
return self._event_data or {}
@property
def title(self):
return self._title
return self._title or ""
@property
def desc(self):
return self._desc
return self._desc or ""
@property
def time_header(self):
return self._time_header
return self._time_header or ""
class TimerRow(Gtk.ListBoxRow):
@@ -357,7 +357,7 @@ class ControlBox(Gtk.HBox):
def on_epg_filter_changed(self, entry):
self._epg_list_box.invalidate_filter()
def epg_filter_function(self, row: EpgRow):
def epg_filter_function(self, row):
txt = self._epg_filter_entry.get_text().upper()
return any((not txt, txt in row.time_header.upper(), txt in row.title.upper(), txt in row.desc.upper()))

View File

@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">1.0.5 Beta</property>
<property name="version">1.0.6 Beta</property>
<property name="copyright">2018-2021 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor for GNU/Linux.</property>

View File

@@ -36,7 +36,7 @@ from .search import SearchProvider
from .service_details_dialog import ServiceDetailsDialog, Action
from .settings_dialog import show_settings_dialog
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
FavClickMode, MOD_MASK)
FavClickMode, MOD_MASK, APP_FONT)
class Application(Gtk.Application):
@@ -136,6 +136,8 @@ class Application(Gtk.Application):
"on_locate_in_services": self.on_locate_in_services,
"on_picons_manager_show": self.on_picons_manager_show,
"on_filter_changed": self.on_filter_changed,
"on_filter_type_toggled": self.on_filter_type_toggled,
"on_filter_satellite_toggled": self.on_filter_satellite_toggled,
"on_assign_picon": self.on_assign_picon,
"on_remove_picon": self.on_remove_picon,
"on_reference_picon": self.on_reference_picon,
@@ -157,10 +159,9 @@ class Application(Gtk.Application):
"on_player_press": self.on_player_press,
"on_full_screen": self.on_full_screen,
"on_http_status_visible": self.on_http_status_visible,
"on_drawing_area_realize": self.on_drawing_area_realize,
"on_player_drawing_area_draw": self.on_player_drawing_area_draw,
"on_player_box_realize": self.on_player_box_realize,
"on_player_box_visibility": self.on_player_box_visibility,
"on_ftp_realize": self.on_ftp_realize,
"on_main_window_state": self.on_main_window_state,
"on_record": self.on_record,
"on_remove_all_unavailable": self.on_remove_all_unavailable,
"on_new_bouquet": self.on_new_bouquet,
@@ -193,8 +194,9 @@ class Application(Gtk.Application):
self._bq_selected = "" # Current selected bouquet
self._select_enabled = True # Multiple selection
# Current satellite positions in the services list
self._sat_positions = []
self._marker_types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name}
self._sat_positions = set()
self._service_types = set()
self._marker_types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
# Player
self._player = None
self._full_screen = False
@@ -208,7 +210,9 @@ class Application(Gtk.Application):
self._links_transmitter = None
self._control_box = None
self._ftp_client = None
# Colors
# Appearance
self._current_font = APP_FONT
self._picons_size = self._settings.list_picon_size
self._use_colors = False
self._NEW_COLOR = None # Color for new services in the main list
self._EXTRA_COLOR = None # Color for services with a extra name for the bouquet
@@ -288,39 +292,37 @@ class Application(Gtk.Application):
self._services_model_filter.set_visible_func(self.services_filter_function)
self._filter_entry = builder.get_object("filter_entry")
self._filter_bar = builder.get_object("filter_bar")
self._filter_types_box = builder.get_object("filter_types_box")
self._filter_sat_positions_box = builder.get_object("filter_sat_positions_box")
self._filter_types_model = builder.get_object("filter_types_list_store")
self._filter_sat_positions_model = builder.get_object("filter_sat_positions_list_store")
self._filter_sat_pos_model = builder.get_object("filter_sat_pos_list_store")
self._filter_only_free_button = builder.get_object("filter_only_free_button")
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
# Player
self._player_box = builder.get_object("player_box")
self._player_event_box = builder.get_object("player_event_box")
self._player_scale = builder.get_object("player_scale")
self._player_full_time_label = builder.get_object("player_full_time_label")
self._player_current_time_label = builder.get_object("player_current_time_label")
self._player_rewind_box = builder.get_object("player_rewind_box")
self._player_drawing_area = builder.get_object("player_drawing_area")
self._player_tool_bar = builder.get_object("player_tool_bar")
self._player_prev_button = builder.get_object("player_prev_button")
self._player_next_button = builder.get_object("player_next_button")
self._player_play_button = builder.get_object("player_play_button")
self._player_box.bind_property("visible", self._services_main_box, "visible", 4)
self._player_box.bind_property("visible", self._bouquets_main_box, "visible", 4)
self._fav_bouquets_paned = builder.get_object("fav_bouquets_paned")
self._player_box.bind_property("visible", builder.get_object("close_player_menu_button"), "visible")
self._player_box.bind_property("visible", builder.get_object("left_header_box"), "visible", 4)
self._player_box.bind_property("visible", builder.get_object("right_header_box"), "visible", 4)
self._player_box.bind_property("visible", builder.get_object("main_popover_menu_box"), "visible", 4)
self._player_box.bind_property("visible", builder.get_object("main_header_box"), "visible", 4)
self._player_box.bind_property("visible", builder.get_object("left_header_separator"), "visible", 4)
self._player_box.bind_property("visible", builder.get_object("tools_button_box"), "visible", 4)
self._player_box.bind_property("visible", self._profile_combo_box, "visible", 4)
self._player_box.bind_property("visible", self._player_event_box, "visible")
self._fav_view.bind_property("sensitive", self._player_prev_button, "sensitive")
self._fav_view.bind_property("sensitive", self._player_next_button, "sensitive")
self._fav_view.bind_property("sensitive", self._bouquets_view, "sensitive")
# Record
self._record_image = builder.get_object("record_button_image")
# Enabling events for the drawing area
self._player_drawing_area.set_events(Gdk.ModifierType.BUTTON1_MASK)
self._player_frame = builder.get_object("player_frame")
# Search
self._search_bar = builder.get_object("search_bar")
self._search_bar.bind_property("search-mode-enabled", self._search_bar, "visible")
@@ -345,7 +347,8 @@ class Application(Gtk.Application):
self.set_accels()
self.init_drag_and_drop()
self.init_colors()
self.init_appearance()
self.filter_set_default()
if self._settings.load_last_config:
config = self._settings.get("last_config") or {}
@@ -502,11 +505,21 @@ class Application(Gtk.Application):
self._fav_view.get_selection().set_select_function(lambda *args: self._select_enabled)
self._bouquets_view.get_selection().set_select_function(lambda *args: self._select_enabled)
def init_colors(self, update=False):
""" Initialisation of background colors for the services.
def init_appearance(self, update=False):
""" Appearance initialisation.
If update=False - first call on program start, else - after options changes!
"""
if self._current_font != self._settings.list_font:
from gi.repository import Pango
font_desc = Pango.FontDescription.from_string(self._settings.list_font)
list(map(lambda v: v.modify_font(font_desc), (self._services_view, self._fav_view, self._bouquets_view)))
self._current_font = self._settings.list_font
if self._picons_size != self._settings.list_picon_size:
self.update_picons_size()
if self._s_type is SettingsType.ENIGMA_2:
self._use_colors = self._settings.use_colors
@@ -522,6 +535,15 @@ class Application(Gtk.Application):
self._NEW_COLOR = new_rgb
self._EXTRA_COLOR = extra_rgb
@run_idle
def update_picons_size(self):
self._picons_size = self._settings.list_picon_size
update_picons_data(self._settings.picons_local_path, self._picons, self._picons_size)
self._fav_model.foreach(lambda m, p, itr: m.set_value(itr, Column.FAV_PICON, self._picons.get(
self._services.get(m.get_value(itr, Column.FAV_ID)).picon_id, None)))
self._services_model.foreach(lambda m, p, itr: m.set_value(itr, Column.SRV_PICON, self._picons.get(
m.get_value(itr, Column.SRV_PICON_ID), None)))
def update_background_colors(self, new_color, extra_color):
if extra_color != self._EXTRA_COLOR:
for row in self._fav_model:
@@ -982,7 +1004,8 @@ class Application(Gtk.Application):
target_column = Column.FAV_ID if target is ViewTarget.FAV else Column.SRV_FAV_ID
srv = self._services.get(model[path][target_column], None)
if srv and srv.picon_id:
tooltip.set_icon(get_picon_pixbuf(self._settings.picons_local_path + srv.picon_id, size=96))
tooltip.set_icon(get_picon_pixbuf(self._settings.picons_local_path + srv.picon_id,
size=self._settings.tooltip_logo_size))
tooltip.set_text(
self.get_hint_for_bq_list(srv) if target is ViewTarget.FAV else self.get_hint_for_srv_list(srv))
view.set_tooltip_row(tooltip, path)
@@ -1444,7 +1467,7 @@ class Application(Gtk.Application):
yield True
services = get_services(data_path, prf, self.get_format_version() if prf is SettingsType.ENIGMA_2 else 0)
yield True
update_picons_data(self._settings.picons_local_path, self._picons)
update_picons_data(self._settings.picons_local_path, self._picons, self._picons_size)
yield True
except FileNotFoundError as e:
msg = get_message("Please, download files from receiver or setup your path for read data!")
@@ -1471,6 +1494,9 @@ class Application(Gtk.Application):
yield True
self._data_hash = self.get_data_hash()
yield True
if self._filter_bar.get_visible():
self.on_filter_changed()
yield True
def append_data(self, bouquets, services):
if self._app_info_box.get_visible():
@@ -1873,7 +1899,7 @@ class Application(Gtk.Application):
c_gen = self.clear_current_data()
yield from c_gen
self.init_colors(True)
self.init_appearance(True)
self.init_profiles()
yield True
gen = self.init_http_api()
@@ -2293,18 +2319,17 @@ class Application(Gtk.Application):
yield True
self._wait_dialog.hide()
# ***************** Backup ********************#
# ***************** Backup ******************** #
def on_backup_tool_show(self, action, value=None):
""" Shows backup tool dialog """
BackupDialog(self._main_window, self._settings, self.open_data).show()
# ***************** Player *********************#
# ***************** Player ********************* #
def on_play_stream(self, item=None):
self.on_player_play()
@run_idle
def on_player_play(self, item=None):
path, column = self._fav_view.get_cursor()
if path:
@@ -2340,9 +2365,9 @@ class Application(Gtk.Application):
self.show_playback_window()
elif self._playback_window:
title = self.get_playback_title()
GLib.idle_add(self._playback_window.set_title, title)
GLib.idle_add(self._player.play, url, priority=GLib.PRIORITY_LOW)
GLib.idle_add(self._playback_window.show)
self._playback_window.set_title(title)
self._playback_window.show()
GLib.idle_add(self._player.play, url)
else:
self.show_error_dialog("Init player error!")
finally:
@@ -2354,11 +2379,13 @@ class Application(Gtk.Application):
if not self._player_box.get_visible():
self.set_player_area_size(self._player_box)
GLib.idle_add(self._player.play, url, priority=GLib.PRIORITY_LOW)
GLib.idle_add(self._player.play, url)
self._player_box.set_visible(True)
def on_player_stop(self, item=None):
if self._player:
self._fav_view.set_sensitive(True)
self._player.stop()
def on_player_previous(self, item):
@@ -2371,6 +2398,7 @@ class Application(Gtk.Application):
@run_with_delay(1)
def set_player_action(self):
self._fav_view.set_sensitive(False)
if self._fav_click_mode is FavClickMode.PLAY:
self.on_stream()
elif self._fav_click_mode is FavClickMode.ZAP_PLAY:
@@ -2390,7 +2418,7 @@ class Application(Gtk.Application):
def on_player_close(self, window=None, event=None):
if self._player:
self._player.stop()
GLib.idle_add(self._player.stop)
self.set_playback_elms_active()
if self._playback_window:
@@ -2406,12 +2434,15 @@ class Application(Gtk.Application):
self._player_scale.get_adjustment().set_upper(duration)
GLib.idle_add(self._player_rewind_box.set_visible, duration > 0, priority=GLib.PRIORITY_LOW)
GLib.idle_add(self._player_current_time_label.set_text, "0", priority=GLib.PRIORITY_LOW)
GLib.idle_add(self._player_full_time_label.set_text, self.get_time_str(duration), priority=GLib.PRIORITY_LOW)
GLib.idle_add(self._player_full_time_label.set_text, self.get_time_str(duration),
priority=GLib.PRIORITY_LOW)
def on_player_time_changed(self, t):
if not self._full_screen and self._player_rewind_box.get_visible():
GLib.idle_add(self._player_current_time_label.set_text, self.get_time_str(t), priority=GLib.PRIORITY_LOW)
GLib.idle_add(self._player_current_time_label.set_text, self.get_time_str(t),
priority=GLib.PRIORITY_LOW)
@run_with_delay(2)
def on_player_error(self):
self.set_playback_elms_active()
self.show_error_dialog("Can't Playback!")
@@ -2427,77 +2458,62 @@ class Application(Gtk.Application):
h, m = divmod(m, 60)
return "{}{:02d}:{:02d}".format(str(h) + ":" if h else "", m, s)
def on_drawing_area_realize(self, widget):
def on_player_box_realize(self, widget):
if not self._player:
try:
self._player = Player.get_instance(mode=self._settings.play_streams_mode,
rewind_cb=self.on_player_duration_changed,
position_cb=self.on_player_time_changed,
error_cb=self.on_player_error,
playing_cb=self.set_playback_elms_active)
except (ImportError, NameError, AttributeError):
self.show_error_dialog("No VLC is found. Check that it is installed!")
self._player = Player.make(name=self._settings.stream_lib,
mode=self._settings.play_streams_mode,
widget=widget,
buf_cb=self.on_player_duration_changed,
position_cb=self.on_player_time_changed,
error_cb=self.on_player_error,
playing_cb=self.set_playback_elms_active)
except (ImportError, NameError) as e:
self.show_error_dialog(str(e))
return True
else:
if self._settings.is_darwin:
self._player.set_nso(widget)
else:
self._player.set_xwindow(widget.get_window().get_xid())
self._main_window.connect("key-press-event", self.on_player_key_press)
self._player.play(self._current_mrl)
finally:
self.set_playback_elms_active()
if self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
self.set_player_area_size(widget)
def on_player_box_visibility(self, box):
visible = box.get_visible()
self._fav_bouquets_paned.set_orientation(Gtk.Orientation.VERTICAL if visible else Gtk.Orientation.HORIZONTAL)
@run_idle
def set_player_area_size(self, widget):
w, h = self._main_window.get_size()
widget.set_size_request(w * 0.6, -1)
def on_player_drawing_area_draw(self, widget, cr):
""" Used for black background drawing in the player drawing area.
Required for Gtk >= 3.20.
More info: https://developer.gnome.org/gtk3/stable/ch32s10.html,
https://developer.gnome.org/gtk3/stable/GtkStyleContext.html#gtk-render-background
"""
context = widget.get_style_context()
width = widget.get_allocated_width()
height = widget.get_allocated_height()
Gtk.render_background(context, cr, 0, 0, width, height)
r, g, b, a = 0, 0, 0, 1 # black color
cr.set_source_rgba(r, g, b, a)
cr.rectangle(0, 0, width, height)
cr.fill()
def on_player_press(self, area, event):
if event.button == Gdk.BUTTON_PRIMARY:
if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
self.on_full_screen()
def on_player_key_press(self, widget, event):
if self._player and self._player_event_box.get_visible():
key = event.keyval
if any((key == Gdk.KEY_F11, key == Gdk.KEY_f, self._full_screen and key == Gdk.KEY_Escape)):
self.on_full_screen()
def on_full_screen(self, item=None):
self._full_screen = not self._full_screen
if self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
self.update_state_on_full_screen(not self._full_screen)
self._main_window.fullscreen() if self._full_screen else self._main_window.unfullscreen()
elif self._playback_window:
self._player_tool_bar.set_visible(not self._full_screen)
self._playback_window.fullscreen() if self._full_screen else self._playback_window.unfullscreen()
def on_main_window_state(self, window, event):
state = event.new_window_state
full = not state & Gdk.WindowState.FULLSCREEN
self._main_data_box.set_visible(full)
self._player_tool_bar.set_visible(full)
self._status_bar_box.set_visible(full)
if not state & Gdk.WindowState.ICONIFIED and self._links_transmitter:
self._links_transmitter.hide()
def update_state_on_full_screen(self, visible):
self._main_data_box.set_visible(visible)
self._player_tool_bar.set_visible(visible)
self._status_bar_box.set_visible(visible and not self._app_info_box.get_visible())
@run_idle
def show_playback_window(self):
self._player_prev_button.set_visible(False)
self._player_next_button.set_visible(False)
self._player_play_button.set_margin_left(5)
width, height = 480, 240
size = self._settings.get("playback_window_size")
if size:
@@ -2509,10 +2525,18 @@ class Application(Gtk.Application):
icon_name="demon-editor")
self._playback_window.resize(width, height)
self._playback_window.connect("delete-event", self.on_player_close)
self._playback_window.connect("key-press-event", self.on_player_key_press)
box = Gtk.HBox(visible=True, orientation="vertical")
self._player_drawing_area.reparent(box)
self._player_box.remove(self._player_tool_bar)
box.pack_end(self._player_tool_bar, False, False, 0)
self._player_event_box.reparent(box)
self._playback_window.bind_property("visible", self._player_event_box, "visible")
if not self._settings.is_darwin:
self._player_prev_button.set_visible(False)
self._player_next_button.set_visible(False)
self._player_box.remove(self._player_tool_bar)
box.pack_end(self._player_tool_bar, False, False, 0)
self._playback_window.add(box)
self._playback_window.set_application(self)
self._playback_window.show()
@@ -2612,7 +2636,7 @@ class Application(Gtk.Application):
""" Switch to the channel and watch in the player """
if not self._app_info_box.get_visible() and self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
self.set_player_area_size(self._player_box)
self._player_box.set_visible(True)
GLib.idle_add(self._player_box.set_visible, True)
GLib.idle_add(self._app_info_box.set_visible, False)
self._http_api.send(HttpAPI.Request.STREAM_CURRENT, None, self.watch)
@@ -2803,32 +2827,29 @@ class Application(Gtk.Application):
return True
action.set_state(value)
if value:
self.update_filter_sat_positions()
self._filter_entry.grab_focus()
else:
self.filter_set_default()
self._filter_entry.grab_focus() if value else self.on_filter_changed()
self.filter_set_default()
self._filter_bar.set_search_mode(value)
@run_idle
def filter_set_default(self):
""" Setting defaults for filter elements. """
self._filter_entry.set_text("")
self._filter_sat_positions_box.set_active(0)
self._filter_types_box.set_active(0)
self._filter_only_free_button.set_active(False)
self._filter_types_model.foreach(lambda m, p, i: m.set_value(i, 1, True))
self._service_types.update({r[0] for r in self._filter_types_model})
self.update_sat_positions()
def init_sat_positions(self):
self._sat_positions.clear()
first = (self._filter_sat_positions_model[0][0],)
self._filter_sat_positions_model.clear()
self._filter_sat_positions_model.append(first)
self._filter_sat_positions_box.set_active(0)
first = self._filter_sat_pos_model[0][:]
self._filter_sat_pos_model.clear()
self._filter_sat_pos_model.append(first)
def update_sat_positions(self):
""" Updates positions values for the filtering function """
""" Updates positions values for the filtering function. """
self._sat_positions.clear()
sat_positions = set()
if self._s_type is SettingsType.ENIGMA_2:
terrestrial = False
@@ -2837,64 +2858,72 @@ class Application(Gtk.Application):
for srv in self._services.values():
tr_type = srv.transponder_type
if tr_type == "s" and srv.pos:
sat_positions.add(srv.pos)
self._sat_positions.add(srv.pos)
elif tr_type == "t":
terrestrial = True
elif tr_type == "c":
cable = True
if terrestrial:
self._sat_positions.append("T")
self._sat_positions.add("T")
if cable:
self._sat_positions.append("C")
self._sat_positions.add("C")
elif self._s_type is SettingsType.NEUTRINO_MP:
list(map(lambda s: sat_positions.add(s.pos), filter(lambda s: s.pos, self._services.values())))
list(map(lambda s: self._sat_positions.add(s.pos), filter(lambda s: s.pos, self._services.values())))
self._sat_positions.extend(map(str, sorted(sat_positions)))
if self._filter_bar.is_visible():
self.update_filter_sat_positions()
self.update_filter_sat_positions()
@run_idle
def update_filter_sat_positions(self):
model = self._filter_sat_positions_model
if len(model) < 2:
list(map(self._filter_sat_positions_model.append, map(lambda x: (str(x),), self._sat_positions)))
else:
selected = self._filter_sat_positions_box.get_active_id()
active = self._filter_sat_positions_box.get_active()
itrs = list(filter(lambda it: model[it][0] not in self._sat_positions, [row.iter for row in model][1:]))
list(map(model.remove, itrs))
""" Updates the values for the satellite positions button model. """
first = self._filter_sat_pos_model[self._filter_sat_pos_model.get_iter_first()][:]
self._filter_sat_pos_model.clear()
self._filter_sat_pos_model.append((first[0], True))
self._sat_positions.discard(first[0])
list(map(lambda pos: self._filter_sat_pos_model.append((pos, True)),
sorted(self._sat_positions, key=self.get_pos_num, reverse=True)))
if active != 0 and selected not in self._sat_positions:
self._filter_sat_positions_box.set_active(0)
@run_with_delay(1)
def on_filter_changed(self, item):
GLib.idle_add(self._services_model_filter.refilter, priority=GLib.PRIORITY_LOW)
@run_with_delay(2)
def on_filter_changed(self, item=None):
model = self._services_view.get_model()
self._services_view.set_model(None)
self._services_model_filter.refilter()
self._services_view.set_model(model)
def services_filter_function(self, model, itr, data):
if self._services_model_filter is None or self._services_model_filter == "None":
if not self._filter_bar.is_visible():
return True
else:
r_txt = str(model.get(itr, Column.SRV_SERVICE, Column.SRV_PACKAGE, Column.SRV_TYPE, Column.SRV_SSID,
Column.SRV_FREQ, Column.SRV_RATE, Column.SRV_POL, Column.SRV_FEC, Column.SRV_SYSTEM,
Column.SRV_POS)).upper()
txt = self._filter_entry.get_text().upper() in r_txt
type_active = self._filter_types_box.get_active() > 0
pos_active = self._filter_sat_positions_box.get_active() > 0
free = not model.get(itr, Column.SRV_CODED)[0] if self._filter_only_free_button.get_active() else True
srv_type, pos = model.get(itr, Column.SRV_TYPE, Column.SRV_POS)
if type_active and pos_active:
active_id = self._filter_types_box.get_active_id() == model.get(itr, Column.SRV_TYPE)[0]
pos = self._filter_sat_positions_box.get_active_id() == model.get(itr, Column.SRV_POS)[0]
return active_id and pos and txt and free
elif type_active:
return self._filter_types_box.get_active_id() == model.get(itr, Column.SRV_TYPE)[0] and txt and free
elif pos_active:
pos = self._filter_sat_positions_box.get_active_id() == model.get(itr, Column.SRV_POS)[0]
return pos and txt and free
return all((srv_type in self._service_types,
pos in self._sat_positions,
txt, free))
return txt and free
def on_filter_type_toggled(self, toggle, path):
self.update_filter_toogle_model(self._filter_types_model, toggle, path, self._service_types)
def on_filter_satellite_toggled(self, toggle, path):
self.update_filter_toogle_model(self._filter_sat_pos_model, toggle, path, self._sat_positions)
def update_filter_toogle_model(self, model, toggle, path, values_set):
active = not toggle.get_active()
if path == "0":
model.foreach(lambda m, p, i: m.set_value(i, 1, active))
else:
model.set_value(model.get_iter(path), 1, active)
if active:
model.set_value(model.get_iter_first(), 1, len({r[0] for r in model if r[1]}) == len(model) - 1)
else:
model.set_value(model.get_iter_first(), 1, False)
values_set.clear()
values_set.update({r[0] for r in model if r[1]})
self.on_filter_changed()
def on_search_toggled(self, action, value):
if self._app_info_box.get_visible():
@@ -3072,7 +3101,7 @@ class Application(Gtk.Application):
@run_task
def update_picons(self):
update_picons_data(self._settings.picons_local_path, self._picons)
update_picons_data(self._settings.picons_local_path, self._picons, self._picons_size)
append_picons(self._picons, self._services_model)
def on_assign_picon(self, view, src_path=None, dst_path=None):

View File

@@ -353,12 +353,12 @@ def scroll_to(index, view, paths=None):
# ***************** Picons *********************#
def update_picons_data(path, picons):
def update_picons_data(path, picons, size=32):
if not os.path.exists(path):
return
for file in os.listdir(path):
pf = get_picon_pixbuf(path + file)
pf = get_picon_pixbuf(path + file, size)
if pf:
picons[file] = pf

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2
<!-- Generated with glade 3.22.1
The MIT License (MIT)
@@ -175,46 +175,147 @@ Author: Dmitriy Yefremov
<signal name="row-deleted" handler="on_model_changed" swapped="no"/>
<signal name="row-inserted" handler="on_model_changed" swapped="no"/>
</object>
<object class="GtkListStore" id="filter_sat_positions_list_store">
<object class="GtkListStore" id="filter_sat_pos_list_store">
<columns>
<!-- column-name satellite -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">All positions</col>
<col id="1">True</col>
</row>
</data>
</object>
<object class="GtkPopover" id="filter_satellite_popover">
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="filter_satellite_scrolled_window">
<property name="width_request">135</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="hscrollbar_policy">never</property>
<property name="max_content_height">350</property>
<property name="propagate_natural_height">True</property>
<child>
<object class="GtkTreeView" id="filter_satellite_view">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">filter_sat_pos_list_store</property>
<property name="headers_visible">False</property>
<property name="enable_search">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="fiter_satellite_column">
<property name="title" translatable="yes">Satellite</property>
<child>
<object class="GtkCellRendererText" id="filter_satelliter_renderer_text"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererToggle" id="filter_satellite_renderer_toggle">
<property name="xalign">0.98000001907348633</property>
<signal name="toggled" handler="on_filter_satellite_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="filter_types_list_store">
<columns>
<!-- column-name type -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">All types</col>
<col id="1">False</col>
</row>
<row>
<col id="0">TV</col>
<col id="1">False</col>
</row>
<row>
<col id="0">TV (H264)</col>
<col id="1">False</col>
</row>
<row>
<col id="0" translatable="yes">TV (HD)</col>
<col id="1">False</col>
</row>
<row>
<col id="0" translatable="yes">TV (UHD)</col>
<col id="1">False</col>
</row>
<row>
<col id="0" translatable="yes">Radio</col>
<col id="1">False</col>
</row>
<row>
<col id="0" translatable="yes">Data</col>
<col id="1">False</col>
</row>
</data>
</object>
<object class="GtkPopover" id="filter_type_popover">
<property name="can_focus">False</property>
<child>
<object class="GtkTreeView" id="filter_type_view">
<property name="width_request">135</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="model">filter_types_list_store</property>
<property name="headers_visible">False</property>
<property name="enable_search">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="fiter_type_column">
<property name="title" translatable="yes">Type</property>
<child>
<object class="GtkCellRendererText" id="filter_type_renderer_text"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererToggle" id="filter_type_renderer_toggle">
<property name="xalign">0.98000001907348633</property>
<signal name="toggled" handler="on_filter_type_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkImage" id="find_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -1049,7 +1150,6 @@ Author: Dmitriy Yefremov
<property name="gravity">center</property>
<property name="startup_id">DemonEditor</property>
<signal name="delete-event" handler="on_close_app" swapped="no"/>
<signal name="window-state-event" handler="on_main_window_state" swapped="no"/>
<child type="titlebar">
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
@@ -1491,13 +1591,16 @@ Author: Dmitriy Yefremov
<property name="width_request">320</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<signal name="hide" handler="on_player_box_visibility" swapped="no"/>
<signal name="show" handler="on_player_box_visibility" swapped="no"/>
<child>
<object class="GtkDrawingArea" id="player_drawing_area">
<property name="visible">True</property>
<object class="GtkEventBox" id="player_event_box">
<property name="can_focus">False</property>
<signal name="button-press-event" handler="on_player_press" swapped="no"/>
<signal name="draw" handler="on_player_drawing_area_draw" swapped="no"/>
<signal name="realize" handler="on_drawing_area_realize" swapped="no"/>
<signal name="realize" handler="on_player_box_realize" swapped="no"/>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
@@ -1763,7 +1866,6 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="filter_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkToggleButton" id="filter_only_free_button">
<property name="label" translatable="yes">Only free</property>
@@ -1795,18 +1897,19 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkComboBox" id="filter_types_box">
<object class="GtkMenuButton" id="filter_types_button">
<property name="width_request">75</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">filter_types_list_store</property>
<property name="active">0</property>
<property name="id_column">0</property>
<signal name="changed" handler="on_filter_changed" swapped="no"/>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="popover">filter_type_popover</property>
<child>
<object class="GtkCellRendererText" id="filter_types_box_cellrenderertext"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
<object class="GtkLabel" id="filter_type_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Type</property>
</object>
</child>
</object>
<packing>
@@ -1816,18 +1919,19 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkComboBox" id="filter_sat_positions_box">
<object class="GtkMenuButton" id="filter_satellite_button">
<property name="width_request">75</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">filter_sat_positions_list_store</property>
<property name="active">0</property>
<property name="id_column">0</property>
<signal name="changed" handler="on_filter_changed" swapped="no"/>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="popover">filter_satellite_popover</property>
<child>
<object class="GtkCellRendererText" id="filter_satellites_box_cellrenderertext"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
<object class="GtkLabel" id="filter_pos_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pos</property>
</object>
</child>
</object>
<packing>
@@ -1836,6 +1940,9 @@ Author: Dmitriy Yefremov
<property name="position">3</property>
</packing>
</child>
<style>
<class name="group"/>
</style>
</object>
</child>
</object>
@@ -3098,7 +3205,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="app_name_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">DemonEditor</property>
<property name="label">DemonEditor</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="3"/>
@@ -3114,7 +3221,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="app_ver_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">1.0.5 Beta</property>
<property name="label">1.0.6 Beta</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>

View File

@@ -661,7 +661,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="model">picons_src_sort_model</property>
<property name="headers_visible">False</property>
<property name="tooltip_column">1</property>
<property name="tooltip_column">0</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_popup_menu" object="picons_src_view_popup_menu" swapped="no"/>
<signal name="drag-data-get" handler="on_picons_view_drag_data_get" swapped="no"/>
@@ -669,6 +669,7 @@ Author: Dmitriy Yefremov
<signal name="drag-drop" handler="on_picons_src_view_drag_drop" swapped="no"/>
<signal name="drag-end" handler="on_picons_src_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="picons_src_view_selection"/>
</child>
@@ -815,13 +816,14 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="model">picons_dst_sort_model</property>
<property name="headers_visible">False</property>
<property name="tooltip_column">1</property>
<property name="tooltip_column">0</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_popup_menu" object="picons_dest_view_popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_picon_activated" swapped="no"/>
<signal name="drag-data-get" handler="on_picons_view_drag_data_get" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="realize" handler="on_picons_dest_view_realize" swapped="no"/>
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="picons_dest_view_selection"/>
</child>

View File

@@ -72,6 +72,7 @@ class PiconsDialog:
"on_fiter_srcs_toggled": self.on_fiter_srcs_toggled,
"on_filter_services_switch": self.on_filter_services_switch,
"on_picon_activated": self.on_picon_activated,
"on_view_query_tooltip": self.on_view_query_tooltip,
"on_tree_view_key_press": self.on_tree_view_key_press,
"on_popup_menu": on_popup_menu}
@@ -753,6 +754,20 @@ class PiconsDialog:
get_message("System"), srv.system, get_message("Freq"), srv.freq,
ref)
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
dest = view.get_dest_row_at_pos(x, y)
if not dest:
return False
path, pos = dest
model = view.get_model()
row = model[path][:]
tooltip.set_icon(get_picon_pixbuf(row[-1], size=self._settings.tooltip_logo_size))
tooltip.set_text(row[1])
view.set_tooltip_row(tooltip, path)
return True
def on_tree_view_key_press(self, view, event):
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):

View File

@@ -3,34 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2020 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
-->
<!--
The MIT License (MIT)
Copyright (c) 2018-2019 Dmitriy Yefremov
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
@@ -542,11 +515,14 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="satellite_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="min_width">250</property>
<property name="title" translatable="yes">Satellite</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="satellite_cellrenderertext"/>
<object class="GtkCellRendererText" id="satellite_cellrenderertext">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
@@ -559,6 +535,7 @@ Author: Dmitriy Yefremov
<property name="min_width">20</property>
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="frequency_cellrenderertext"/>
<attributes>
@@ -573,6 +550,7 @@ Author: Dmitriy Yefremov
<property name="min_width">20</property>
<property name="title" translatable="yes">Rate</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="sat_rate_cellrenderertext"/>
<attributes>
@@ -587,6 +565,7 @@ Author: Dmitriy Yefremov
<property name="min_width">20</property>
<property name="title" translatable="yes">Pol</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="sat_pol_cellrenderertext"/>
<attributes>
@@ -601,6 +580,7 @@ Author: Dmitriy Yefremov
<property name="min_width">20</property>
<property name="title" translatable="yes">FEC</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="set_fec_cellrenderertext"/>
<attributes>
@@ -615,6 +595,7 @@ Author: Dmitriy Yefremov
<property name="min_width">20</property>
<property name="title" translatable="yes">System</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="sys_cellrenderertext"/>
<attributes>
@@ -629,6 +610,7 @@ Author: Dmitriy Yefremov
<property name="min_width">20</property>
<property name="title" translatable="yes">Mod</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="mod_cellrenderertext"/>
<attributes>
@@ -1314,20 +1296,6 @@ Author: Dmitriy Yefremov
<object class="GtkTreeModelSort" id="update_sat_list_model_sort">
<property name="model">update_sat_list_model_filter</property>
</object>
<object class="GtkListStore" id="update_source_store">
<columns>
<!-- column-name source -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0">FlySat</col>
</row>
<row>
<col id="0">LyngSat</col>
</row>
</data>
</object>
<object class="GtkListStore" id="update_service_store">
<columns>
<!-- column-name picon -->
@@ -1392,22 +1360,20 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkComboBox" id="source_combo_box">
<object class="GtkComboBoxText" id="source_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">update_source_store</property>
<property name="active">0</property>
<child>
<object class="GtkCellRendererText" id="source_cellrenderertext"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
<items>
<item id="FLYSAT" translatable="yes">FlySat</item>
<item id="LYNGSAT" translatable="yes">LyngSat</item>
<item id="KINGOFSAT" translatable="yes">KingOfSat</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">1</property>
</packing>
</child>
<child>

View File

@@ -253,13 +253,22 @@ class SatellitesDialog:
return paths
@staticmethod
def on_remove(view):
@run_idle
def on_remove(self, view):
""" Removal of selected satellites and transponders.
The satellites are removed first! Then transponders.
"""
selection = view.get_selection()
model, paths = selection.get_selected_rows()
for itr in [model.get_iter(path) for path in paths]:
model.remove(itr)
itrs = [model.get_iter(path) for path in paths]
satellites = list(filter(model.iter_has_child, itrs))
if len(satellites):
# Removing selected satellites.
list(map(model.remove, satellites))
else:
# Removing selected transponders.
list(map(model.remove, itrs))
@run_idle
def on_save(self, view):
@@ -529,7 +538,13 @@ class UpdateDialog:
@run_task
def get_sat_list(self, src, callback):
sats = self._parser.get_satellites_list(SatelliteSource.FLYSAT if src == 0 else SatelliteSource.LYNGSAT)
sat_src = SatelliteSource.FLYSAT
if src == 1:
sat_src = SatelliteSource.LYNGSAT
elif src == 2:
sat_src = SatelliteSource.KINGOFSAT
sats = self._parser.get_satellites_list(sat_src)
if sats:
callback(sats)
self.is_download = False
@@ -728,8 +743,8 @@ class ServicesUpdateDialog(UpdateDialog):
self._services_parser = ServicesParser(source=SatelliteSource.LYNGSAT)
self._transponder_paned.set_visible(True)
s_model = self._source_box.get_model()
s_model.remove(s_model.get_iter_first())
self._source_box.remove(0)
self._source_box.remove(1)
self._source_box.set_active(0)
# Transponder view popup menu
tr_popup_menu = Gtk.Menu()

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ from app.connections import test_telnet, test_ftp, TestException, test_http, Htt
from app.settings import SettingsType, Settings, PlayStreamsMode
from app.ui.dialogs import show_dialog, DialogType, get_message, get_chooser_dialog
from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT
def show_settings_dialog(transient, options):
@@ -50,6 +50,7 @@ class SettingsDialog:
"on_apply_presets": self.on_apply_presets,
"on_digit_entry_changed": self.on_digit_entry_changed,
"on_view_popup_menu": self.on_view_popup_menu,
"on_list_font_reset": self.on_list_font_reset,
"on_theme_changed": self.on_theme_changed,
"on_theme_add": self.on_theme_add,
"on_theme_remove": self.on_theme_remove,
@@ -124,18 +125,24 @@ class SettingsDialog:
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")
# Program
self._before_save_switch = builder.get_object("before_save_switch")
self._before_downloading_switch = builder.get_object("before_downloading_switch")
self._enable_experimental_box = builder.get_object("enable_experimental_box")
self._colors_grid = builder.get_object("colors_grid")
self._set_color_switch = builder.get_object("set_color_switch")
self._new_color_button = builder.get_object("new_color_button")
self._extra_color_button = builder.get_object("extra_color_button")
self._load_on_startup_switch = builder.get_object("load_on_startup_switch")
self._bouquet_hints_switch = builder.get_object("bouquet_hints_switch")
self._services_hints_switch = builder.get_object("services_hints_switch")
self._lang_combo_box = builder.get_object("lang_combo_box")
# Appearance
self._list_font_button = builder.get_object("list_font_button")
self._picons_size_button = builder.get_object("picons_size_button")
self._tooltip_logo_size_button = builder.get_object("tooltip_logo_size_button")
self._colors_grid = builder.get_object("colors_grid")
self._set_color_switch = builder.get_object("set_color_switch")
self._new_color_button = builder.get_object("new_color_button")
self._extra_color_button = builder.get_object("extra_color_button")
# Extra
self._support_http_api_switch = builder.get_object("support_http_api_switch")
self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
@@ -178,19 +185,19 @@ class SettingsDialog:
self.init_profiles()
if self._settings.is_darwin:
# Appearance
self._appearance_box = builder.get_object("appearance_box")
self._appearance_box.set_visible(True)
# Themes
self._layout_switch = builder.get_object("layout_switch")
self._layout_switch.bind_property("active", builder.get_object("bouquet_box"), "sensitive")
self._layout_switch.set_active(self._ext_settings.alternate_layout)
self._theme_frame = builder.get_object("theme_frame")
self._theme_frame.set_visible(True)
self._theme_thumbnail_image = builder.get_object("theme_thumbnail_image")
self._theme_combo_box = builder.get_object("theme_combo_box")
self._icon_theme_combo_box = builder.get_object("icon_theme_combo_box")
self._dark_mode_switch = builder.get_object("dark_mode_switch")
self._layout_switch = builder.get_object("layout_switch")
self._layout_switch.bind_property("active", builder.get_object("bouquet_box"), "sensitive")
self._themes_support_switch = builder.get_object("themes_support_switch")
self._themes_support_switch.bind_property("active", builder.get_object("gtk_theme_frame"), "sensitive")
self._themes_support_switch.bind_property("active", builder.get_object("icon_theme_frame"), "sensitive")
self.init_appearance()
self._themes_support_switch.bind_property("active", self._theme_frame, "sensitive")
self.init_themes()
@run_idle
def init_ui_elements(self, s_type):
@@ -269,6 +276,7 @@ class SettingsDialog:
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._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)
@@ -276,6 +284,9 @@ class SettingsDialog:
self._transcoding_switch.set_active(self._settings.activate_transcoding)
self._presets_combo_box.set_active_id(self._settings.active_preset)
self.on_transcoding_preset_changed(self._presets_combo_box)
self._picons_size_button.set_active_id(str(self._settings.list_picon_size))
self._tooltip_logo_size_button.set_active_id(str(self._settings.tooltip_logo_size))
self._list_font_button.set_font(self._settings.list_font)
if self._s_type is SettingsType.ENIGMA_2:
self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
@@ -331,6 +342,7 @@ class SettingsDialog:
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.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()
@@ -340,6 +352,9 @@ class SettingsDialog:
self._ext_settings.records_path = self._record_data_dir_field.get_text()
self._ext_settings.activate_transcoding = self._transcoding_switch.get_active()
self._ext_settings.active_preset = self._presets_combo_box.get_active_id()
self._ext_settings.list_picon_size = int(self._picons_size_button.get_active_id())
self._ext_settings.tooltip_logo_size = int(self._tooltip_logo_size_button.get_active_id())
self._ext_settings.list_font = self._list_font_button.get_font()
if self._ext_settings.is_darwin:
self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
@@ -597,9 +612,14 @@ class SettingsDialog:
return FavClickMode.DISABLED
def on_play_mode_changed(self, button):
if self._main_stack.get_visible_child_name() != "streaming":
if self._main_stack.get_visible_child_name() != "streaming" or not button.get_active():
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)
@@ -610,6 +630,9 @@ class SettingsDialog:
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
@@ -620,6 +643,18 @@ class SettingsDialog:
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"
def on_transcoding_preset_changed(self, button):
presets = self._settings.transcoding_presets
prs = presets.get(button.get_active_id())
@@ -664,6 +699,11 @@ class SettingsDialog:
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
menu.popup(None, None, None, None, event.button, event.time)
def on_list_font_reset(self, button):
self._list_font_button.set_font(APP_FONT)
# ******************* Themes *********************** #
def on_theme_changed(self, button):
if self._main_stack.get_visible_child_name() != "appearance":
return
@@ -702,7 +742,7 @@ class SettingsDialog:
response = get_chooser_dialog(self._dialog, self._settings, "Themes Archive [*.xz, *.zip]", ("*.xz", "*.zip"))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self._appearance_box.set_sensitive(False)
self._theme_frame.set_sensitive(False)
self.unpack_theme(response, path, button)
@run_task
@@ -719,7 +759,6 @@ class SettingsDialog:
log("Unpacking end.")
finally:
self.update_theme_button(button, dst)
self._appearance_box.set_sensitive(True)
@run_idle
def update_theme_button(self, button, dst):
@@ -732,6 +771,7 @@ class SettingsDialog:
button.append(theme, theme)
button.set_active_id(theme)
self.show_info_message("Done!", Gtk.MessageType.INFO)
self._theme_frame.set_sensitive(True)
@run_idle
def remove_theme(self, button, path):
@@ -755,9 +795,8 @@ class SettingsDialog:
button.set_active(0)
@run_idle
def init_appearance(self):
def init_themes(self):
self._dark_mode_switch.set_active(self._ext_settings.dark_mode)
self._layout_switch.set_active(self._ext_settings.alternate_layout)
t_support = self._ext_settings.is_themes_support
self._themes_support_switch.set_active(t_support)
if t_support:

View File

@@ -2,7 +2,6 @@ import locale
import os
from enum import Enum, IntEnum
from functools import lru_cache
from app.settings import Settings, SettingsException, IS_DARWIN
import gi
@@ -11,6 +10,8 @@ gi.require_version("Gdk", "3.0")
gi.require_version("Notify", "0.7")
from gi.repository import Gtk, Gdk, Notify
from app.settings import Settings, SettingsException, IS_DARWIN
# Init notify
Notify.init("DemonEditor")
# Setting mod mask for the keyboard depending on the platform.
@@ -20,6 +21,7 @@ UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demo
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
# Translation.
TEXT_DOMAIN = "demon-editor"
APP_FONT = None
try:
settings = Settings.get_instance()
@@ -30,8 +32,12 @@ else:
if UI_RESOURCES_PATH == "app/ui/":
locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
st = Gtk.Settings().get_default()
APP_FONT = st.get_property("gtk-font-name")
if not settings.list_font:
settings.list_font = APP_FONT
if settings.is_themes_support:
st = Gtk.Settings().get_default()
st.set_property("gtk-theme-name", settings.theme)
st.set_property("gtk-icon-theme-name", settings.icon_theme)

View File

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

View File

@@ -1,9 +1,10 @@
Package: demon-editor
Version: 1.0.5-Beta
Version: 1.0.6-Beta
Section: utils
Priority: optional
Architecture: all
Essential: no
Depends: python3 (>= 3.5)
Depends: python3 (>= 3.5), python3-requests
Recommends: gstreamer1.0-gtk3, python3-gi-cairo, python3-chardet
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
Description: Enigma2 channel and satellite list editor

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 Dmitriy Yefremov
# Copyright (C) 2018-2021 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -909,7 +909,7 @@ msgid "Sample rate (Hz):"
msgstr "Частата дыскр. (Гц):"
msgid "Play streams mode:"
msgstr "Рэжым прайгравання струменяў:"
msgstr "Рэжым прайгравання патокаў:"
msgid "Built-in player"
msgstr "Убудаваны плэер"
@@ -1216,3 +1216,15 @@ msgstr "Загрузіць пiконы"
msgid "Errors:"
msgstr "Памылак:"
msgid "Use to play streams:"
msgstr "Скарыстаць для прайгравання патокаў:"
msgid "Font in the lists:"
msgstr "Шрыфт у спісах:"
msgid "Picons size in the lists:"
msgstr "Памер пiконаў у спісах:"
msgid "Logo size in tooltips:"
msgstr "Памер лагатыпа ва ўсплыўных падказках:"

View File

@@ -1,8 +1,8 @@
# Copyright (C) 2018-2020 Dmitriy Yefremov
# Copyright (C) 2018-2021 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
# Charly, 2019.
# Dmitriy Yefremov, 2020.
# Dmitriy Yefremov, 2020-2021.
msgid ""
msgstr ""
"Last-Translator: Dmitriy Yefremov\n"
@@ -31,7 +31,7 @@ msgid "Freq"
msgstr "Freq"
msgid "Rate"
msgstr "Bewertung"
msgstr "SR"
msgid "Pol"
msgstr "Pol."
@@ -1229,3 +1229,15 @@ msgstr "Download picons"
msgid "Errors:"
msgstr "Fehler:"
msgid "Use to play streams:"
msgstr "Zum Abspielen von Streams verwenden:"
msgid "Font in the lists:"
msgstr "Schrift in den Listen:"
msgid "Picons size in the lists:"
msgstr "Picons Größe in den Listen:"
msgid "Logo size in tooltips:"
msgstr "Logo-Größe in Tooltips:"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 Dmitriy Yefremov
# Copyright (C) 2018-2021 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -1213,3 +1213,15 @@ msgstr "Загрузить пиконы"
msgid "Errors:"
msgstr "Ошибок:"
msgid "Use to play streams:"
msgstr "Использовать для воспроизведения потоков:"
msgid "Font in the lists:"
msgstr "Шрифт в списках:"
msgid "Picons size in the lists:"
msgstr "Размер пиконов в списках:"
msgid "Logo size in tooltips:"
msgstr "Размер логотипа во всплывающих подсказках:"

View File

@@ -3,13 +3,13 @@ msgstr ""
"Project-Id-Version: DemonEditor\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
"PO-Revision-Date: 2020-06-08 21:53+0300\n"
"PO-Revision-Date: 2021-02-22 23:53+0300\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
"X-Generator: Poedit 2.4.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: tr\n"
@@ -107,6 +107,9 @@ msgstr "Varsayılan adı ayarla"
msgid "Insert marker"
msgstr "İşaretçi ekle"
msgid "Insert space"
msgstr "Boşluk ekle"
msgid "Locate in services"
msgstr "Hizmetlerde bulun"
@@ -518,6 +521,9 @@ msgstr "Lütfen sadece bir ürün seçiniz!"
msgid "No png file is selected!"
msgstr "Hiçbir png dosyası seçilmedi!"
msgid "No profile selected!"
msgstr "Profil seçilmedi!"
msgid "No reference is present!"
msgstr "Referans yok!"
@@ -923,8 +929,8 @@ msgstr "Akışları oynatma modu:"
msgid "Built-in player"
msgstr "Dahili oynatıcı"
msgid "VLC media player"
msgstr "VLC media player"
msgid "In a separate window"
msgstr "Ayrı bir pencerede"
msgid "Only get m3u file"
msgstr "Sadece m3u dosyası al"
@@ -988,3 +994,254 @@ msgstr "Alıcıdaki tüm piconları kaldırın"
msgid "Service reference"
msgstr "Servis referansı"
msgid "Enable support for"
msgstr "Temalar için desteği etkinleştir"
msgid "Auto-check for updates"
msgstr "Güncellemeleri otomatik kontrol et"
msgid "Filter services"
msgstr "Services filtrele"
msgid "Filter services in the main list."
msgstr "Ana listedeki services filtreleyin."
msgid "Destination:"
msgstr "Hedef:"
msgid "EXPERIMENTAL!"
msgstr "EXPERIMENTAL!"
msgid "Sorting data..."
msgstr "Verilerin sıralanması..."
msgid ""
"There are unsaved changes.\n"
"\n"
"\t Save them now?"
msgstr ""
"Kaydedilmemiş değişiklikler var.\n"
"\n"
"\tŞimdi kaydedilsin mi?"
msgid ""
"Are you sure you want to change the order\n"
"\t of services in this bouquet?"
msgstr ""
"Sırayı değiştirmek istediğinizden emin misiniz\n"
"\t Bu buketdeki hizmetlerin sayısı?"
msgid "Remove from the receiver"
msgstr "Alıcıdaki tüm piconları kaldırın"
msgid "Screenshot"
msgstr "Ekran görüntüsü"
msgid "Video"
msgstr "Video"
msgid "The Neutrino has only experimental support. Not all features are supported!"
msgstr "Neutrino'nun yalnızca experimental desteği var. Tüm özellikler desteklenmiyor!"
msgid "Enable experimental features"
msgstr "Experimental özellikleri etkinleştirin"
msgid "Can't Playback!"
msgstr "Oynatılamıyor!"
msgid "Enable Dark Mode"
msgstr "Koyu Modu Etkinleştir"
msgid "Extract..."
msgstr "Çıkart..."
msgid "Unsupported format!"
msgstr "Desteklenmeyen format!"
msgid "Combine with the current data?"
msgstr "Mevcut verilerle birleştirilsin mi?"
msgid "Importing data done!"
msgstr "Verilerin içe aktarılması tamamlandı!"
msgid "Current service"
msgstr "Mevcut service"
msgid "Open folder"
msgstr "Dosya aç"
msgid "Open archive"
msgstr "Arşiv aç"
msgid "Import from Web"
msgstr "Web'den içe aktar"
msgid "Control"
msgstr "Control"
msgid "Timers"
msgstr "Zamanlayıcılar"
msgid "Timer"
msgstr "Zamanlayıcı"
msgid "Add timer"
msgstr "Zamanlayıcı ekle"
msgid "Hr."
msgstr "Hr."
msgid "Min."
msgstr "Min."
msgid "Power"
msgstr "Power"
msgid "Standby"
msgstr "Standby"
msgid "Wake Up"
msgstr "Wake Up"
msgid "Reboot"
msgstr "Reboot"
msgid "Restart GUI"
msgstr "Restart GUI"
msgid "Shutdown"
msgstr "Shutdown"
msgid "Shut down"
msgstr "Kapat"
msgid "Do Nothing"
msgstr "Hiçbir şey yapma"
msgid "Auto"
msgstr "Auto"
msgid "Grab screenshot"
msgstr "Ekran görüntüsü al"
msgid "Enabled:"
msgstr "Etkin:"
msgid "Name:"
msgstr "Ad:"
msgid "Description:"
msgstr "Açıklama:"
msgid "Service:"
msgstr "Service:"
msgid "Service reference:"
msgstr "Servis referansı:"
msgid "Event ID:"
msgstr "Olay Kimliği:"
msgid "Begins:"
msgstr "Başlıyor:"
msgid "Ends:"
msgstr "Bitiş:"
msgid "Repeated:"
msgstr "Tekrar:"
msgid "Action:"
msgstr "Aksiyon:"
msgid "After event:"
msgstr "Olaydan sonra:"
msgid "Location:"
msgstr "Konum:"
msgid "Mo"
msgstr "Pzt"
msgid "Tu"
msgstr "Sal"
msgid "We"
msgstr "Çar"
msgid "Th"
msgstr "Per"
msgid "Fr"
msgstr "Cum"
msgid "Sa"
msgstr "Cmt"
msgid "Su"
msgstr "Paz"
msgid "Set"
msgstr "Yüklemek"
msgid "Services update"
msgstr "Servisleri güncelle"
msgid "Create folder"
msgstr "Klasör oluştur"
msgid "FTP client"
msgstr "FTP istemcisi"
msgid "The file size is too large!"
msgstr "Dosya boyutu çok büyük!"
msgid "Connect"
msgstr "Bağlan"
msgid "Disconnect"
msgstr "Bağlantıyı kes"
msgid "Size"
msgstr "Boyut"
msgid "Date"
msgstr "Saat"
msgid "Attr."
msgstr "Özellik."
msgid "Toggle display position"
msgstr "Görüntü konumunu değiştir"
msgid "Alternatives"
msgstr "Alternatifler"
msgid "Add alternatives"
msgstr "Alternatif ekleyin"
msgid "DreamOS only!"
msgstr "Sadece DreamOS!"
msgid "A similar service is already in this list!"
msgstr "Bu listede zaten benzer bir hizmet var!"
msgid ""
"Play mode has been changed!\n"
"Restart the program to apply the settings."
msgstr ""
"Oynatma modu değiştirildi!\n"
"Ayarları uygulamak için programı yeniden başlatın."
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr "Piconların doğru isimlendirilmesi için TID, NID ve Namespace değerlerini ayarlayın!"
msgid "Streams detected:"
msgstr "Akışlar algılandı:"
msgid "Download picons"
msgstr "Picon'lar indirin"
msgid "Errors:"
msgstr "Hatalar:"