Compare commits

..

10 Commits

Author SHA1 Message Date
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
DYefremov
3be9b374c8 bump version 2022-01-29 18:43:36 +03:00
DYefremov
0b9fd37ee9 minor revision and improvement of the FTP client 2022-01-28 16:31:12 +03:00
DYefremov
be90d9694a fixed removal of unused picons (#62) 2022-01-28 00:37:02 +03:00
DYefremov
a5144e8e34 fixed multiple selection with the Shift key (#61) 2022-01-27 22:46:34 +03:00
DYefremov
04e0a25956 minor fixes for picons 2022-01-26 23:07:41 +03:00
15 changed files with 218 additions and 75 deletions

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.0", "Referer": ""}
_HEADER = {"User-Agent": "DemonEditor/2.1.2", "Referer": ""}
_LINK_PATTERN = re.compile(r"((.*)-\d+x\d+)-(.*)_by_chocholousek.7z$")
_FILE_PATTERN = re.compile(b"\\s+(1_.*\\.png).*")

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">2.1.0 Beta</property>
<property name="version">2.1.2 Beta</property>
<property name="copyright">2018-2021 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2020 Dmitriy Yefremov
Copyright (c) 2018-2022 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -47,6 +47,11 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="icon_name">folder-new</property>
</object>
<object class="GtkImage" id="file_download_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-transmit</property>
</object>
<object class="GtkListStore" id="file_list_store">
<columns>
<!-- column-name icon -->
@@ -68,6 +73,11 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="icon_name">folder-new</property>
</object>
<object class="GtkImage" id="ftp_download_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-receive</property>
</object>
<object class="GtkListStore" id="ftp_list_store">
<columns>
<!-- column-name icon -->
@@ -716,6 +726,23 @@ Author: Dmitriy Yefremov
<object class="GtkMenu" id="ftp_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="ftp_download_menu_item">
<property name="label" translatable="yes">Receive</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">ftp_download_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_ftp_copy" swapped="no"/>
<accelerator key="F5" signal="activate"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="ftp_create_folder_menu_item">
<property name="label" translatable="yes">Create folder</property>
@@ -746,7 +773,7 @@ Author: Dmitriy Yefremov
<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"/>
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="r" signal="activate" modifiers="Primary"/>
<accelerator key="F2" signal="activate"/>
</object>
</child>
@@ -776,6 +803,23 @@ Author: Dmitriy Yefremov
<object class="GtkMenu" id="file_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="file_download_menu_item">
<property name="label" translatable="yes">Send</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">file_download_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_file_copy" swapped="no"/>
<accelerator key="F5" signal="activate"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="file_create_folder_menu_item">
<property name="label" translatable="yes">Create folder</property>
@@ -795,7 +839,7 @@ Author: Dmitriy Yefremov
<property name="image">rename_image_2</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_file_rename" object="file_name_column_renderer" swapped="no"/>
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="r" signal="activate" modifiers="Primary"/>
<accelerator key="F2" signal="activate"/>
</object>
</child>

View File

@@ -41,7 +41,7 @@ from gi.repository import GLib
from app.commons import log, run_task, run_idle
from app.connections import UtfFTP
from app.settings import IS_LINUX, IS_DARWIN, IS_WIN
from app.settings import IS_LINUX, IS_DARWIN, IS_WIN, SEP
from app.ui.dialogs import show_dialog, DialogType, get_builder
from app.ui.main_helper import on_popup_menu
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, IS_GNOME_SESSION
@@ -162,8 +162,10 @@ class FtpClientBox(Gtk.HBox):
"on_ftp_edit": self.on_ftp_edit,
"on_ftp_rename": self.on_ftp_rename,
"on_ftp_renamed": self.on_ftp_renamed,
"on_ftp_copy": self.on_ftp_copy,
"on_file_rename": self.on_file_rename,
"on_file_renamed": self.on_file_renamed,
"on_file_copy": self.on_file_copy,
"on_file_remove": self.on_file_remove,
"on_ftp_remove": self.on_ftp_file_remove,
"on_file_create_folder": self.on_file_create_folder,
@@ -485,6 +487,14 @@ class FtpClientBox(Gtk.HBox):
row[self.Column.NAME] = new_value
row[self.Column.ATTR] = str(new_path.resolve())
def on_file_copy(self, item=None):
uris = self.get_file_uris()
self.copy_to_ftp(uris) if uris else None
def on_ftp_copy(self, item=None):
uris = self.get_ftp_uris()
self.copy_to_pc(uris) if uris else None
def on_file_remove(self, item=None):
if show_dialog(DialogType.QUESTION, self._app.app_window) != Gtk.ResponseType.OK:
return
@@ -598,30 +608,43 @@ class FtpClientBox(Gtk.HBox):
return True
def on_ftp_drag_data_get(self, view, context, data, info, time):
model, paths = view.get_selection().get_selected_rows()
uris = self.get_ftp_uris()
data.set_uris(uris) if uris else None
def get_ftp_uris(self):
""" Returns the selected paths in FTP view as a list containing uris string or None. """
model, paths = self._ftp_view.get_selection().get_selected_rows()
if len(paths) > 0:
sep = self.URI_SEP if self._settings.is_darwin else "\n"
uris = []
for r in [model[p][:] for p in paths]:
if r[self.Column.SIZE] != self.LINK and r[self.Column.NAME] != self.ROOT:
uris.append(Path(f"/{r[self.Column.NAME]}:{r[self.Column.ATTR]}").as_uri())
data.set_uris([sep.join(uris)])
path = Path(f"/{r[self.Column.NAME]}:{r[self.Column.ATTR]}")
uris.append(str(path.resolve()) if IS_WIN else path.as_uri())
return [sep.join(uris)]
@run_task
def on_ftp_drag_data_received(self, view, context, x, y, data: Gtk.SelectionData, info, time):
if not self._ftp:
return
self.copy_to_ftp(data.get_uris())
Gtk.drag_finish(context, True, False, time)
return True
@run_task
def copy_to_ftp(self, uris):
resp = "2"
try:
GLib.idle_add(self._app.wait_dialog.show)
uris = data.get_uris()
if self._settings.is_darwin and len(uris) == 1:
uris = uris[0].split(self.URI_SEP)
if len(uris) == 1:
uris = uris[0].split(self.URI_SEP if self._settings.is_darwin else "\n")
for uri in uris:
uri = urlparse(unquote(uri)).path
if IS_WIN:
uri = uri.lstrip("/")
path = Path(uri)
if path.is_dir():
try:
@@ -629,34 +652,40 @@ class FtpClientBox(Gtk.HBox):
except all_errors as e:
pass # NOP
self._ftp.cwd(path.name)
resp = self._ftp.upload_dir(str(path.resolve()) + "/", self.update_ftp_info)
resp = self._ftp.upload_dir(str(path.resolve()) + SEP, self.update_ftp_info)
else:
resp = self._ftp.send_file(path.name, str(path.parent) + "/", callback=self.update_ftp_info)
resp = self._ftp.send_file(path.name, str(path.parent) + SEP, callback=self.update_ftp_info)
finally:
GLib.idle_add(self._app.wait_dialog.hide)
if resp and resp[0] == "2":
itr = self._ftp_model.get_iter_first()
if itr:
self.init_ftp_data(self._ftp_model.get_value(itr, self.Column.ATTR))
def on_file_drag_data_get(self, view, context, data, info, time):
uris = self.get_file_uris()
data.set_uris(uris) if uris else None
def get_file_uris(self):
""" Returns the selected paths in the file view as a list containing uris string or None. """
model, paths = self._file_view.get_selection().get_selected_rows()
if len(paths) > 0:
sep = self.URI_SEP if self._settings.is_darwin else "\n"
return [sep.join([Path(model[p][self.Column.ATTR]).as_uri() for p in paths])]
def on_file_drag_data_received(self, view, context, x, y, data, info, time):
self.copy_to_pc(data.get_uris())
Gtk.drag_finish(context, True, False, time)
return True
def on_file_drag_data_get(self, view, context, data: Gtk.SelectionData, info, time):
model, paths = view.get_selection().get_selected_rows()
if len(paths) > 0:
sep = self.URI_SEP if self._settings.is_darwin else "\n"
uris = [sep.join([Path(model[p][self.Column.ATTR]).as_uri() for p in paths])]
data.set_uris(uris)
@run_task
def on_file_drag_data_received(self, view, context, x, y, data, info, time):
def copy_to_pc(self, uris):
cur_path = self._file_model.get_value(self._file_model.get_iter_first(), self.Column.ATTR) + "/"
try:
GLib.idle_add(self._app.wait_dialog.show)
uris = data.get_uris()
if self._settings.is_darwin and len(uris) == 1:
uris = uris[0].split(self.URI_SEP)
if len(uris) == 1:
uris = uris[0].split(self.URI_SEP if self._settings.is_darwin else "\n")
for uri in uris:
name, sep, attr = unquote(Path(uri).name).partition(":")
@@ -673,9 +702,6 @@ class FtpClientBox(Gtk.HBox):
GLib.idle_add(self._app.wait_dialog.hide)
self.init_file_data(cur_path)
Gtk.drag_finish(context, True, False, time)
return True
def on_view_drag_end(self, view, context):
self._select_enabled = True
view.get_selection().unselect_all()
@@ -707,6 +733,11 @@ class FtpClientBox(Gtk.HBox):
elif key is KeyboardKey.F4:
if self._ftp_view.is_focus():
self.on_ftp_edit()
elif key is KeyboardKey.F5:
if self._ftp_view.is_focus():
self.on_ftp_copy()
elif self._file_view.is_focus():
self.on_file_copy()
elif key is KeyboardKey.DELETE:
if self._ftp_view.is_focus():
self.on_ftp_file_remove()

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

@@ -630,7 +630,7 @@ class M3uImportDialog(IptvListDialog):
super().__init__(transient, s_type)
self._app = app
self._picons = app._picons
self._picons = app.picons
self._pic_path = app._settings.profile_picons_path
self._services = None
self._url_count = 0
@@ -802,7 +802,7 @@ class M3uImportDialog(IptvListDialog):
def update_fav_model(self):
services = self._app.current_services
picons = self._app._picons
picons = self._app.picons
model = self._app.fav_view.get_model()
for r in model:
s = services.get(r[Column.FAV_ID], None)

View File

@@ -1426,7 +1426,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">2.1.0 Beta</property>
<property name="label">2.1.2 Beta</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>

View File

@@ -47,7 +47,7 @@ from app.eparser.enigma.bouquets import BqServiceType
from app.eparser.iptv import export_to_m3u
from app.eparser.neutrino.bouquets import BqType
from app.settings import (SettingsType, Settings, SettingsException, SettingsReadException,
IS_DARWIN, PlayStreamsMode)
IS_DARWIN, PlayStreamsMode, IS_LINUX)
from app.tools.media import Recorder
from app.ui.control import ControlTool, EpgTool, TimerTool, RecordingsTool
from app.ui.epg import EpgDialog
@@ -788,7 +788,7 @@ class Application(Gtk.Application):
def force_ctrl(self, view, event):
""" Function for force ctrl press event for view """
if event.state is not Gdk.ModifierType.SHIFT_MASK:
if not event.state & Gdk.ModifierType.SHIFT_MASK:
event.state |= MOD_MASK
def on_close_app(self, *args):
@@ -1590,13 +1590,14 @@ class Application(Gtk.Application):
p_itr = model.iter_parent(itr)
if not p_itr:
break
if p_itr and model.get_path(p_itr)[0] == p_path:
if all((IS_LINUX, p_itr, model.get_path(p_itr)[0] == p_path)):
model.move_after(itr, top_iter)
top_iter = itr
else:
model.insert(parent_itr, model.get_path(top_iter)[1], model[itr][:])
to_del.append(itr)
elif not model.iter_has_child(top_iter):
elif not model.iter_has_child(top_iter) or not IS_LINUX:
for itr in itrs:
model.append(top_iter, model[itr][:])
to_del.append(itr)
@@ -1604,6 +1605,7 @@ class Application(Gtk.Application):
list(map(model.remove, to_del))
self.update_bouquets_type()
drag_context.finish(True, False, time)
def get_selection(self, view):
""" Creates a string from the iterators of the selected rows """
@@ -3588,7 +3590,7 @@ class Application(Gtk.Application):
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
return
remove_all_unused_picons(self._settings, self._picons, self._services.values())
remove_all_unused_picons(self._settings, self._services.values())
def get_target_view(self, view):
return ViewTarget.SERVICES if Gtk.Buildable.get_name(view) == "services_tree_view" else ViewTarget.FAV
@@ -3855,6 +3857,10 @@ class Application(Gtk.Application):
def current_bouquets(self):
return self._bouquets
@property
def picons(self):
return self._picons
@property
def picons_buffer(self):
""" Returns a copy and clears the current buffer. """

View File

@@ -37,6 +37,7 @@ __all__ = ("insert_marker", "move_items", "rename", "ViewTarget", "set_flags", "
import os
import shutil
from collections import defaultdict
from pathlib import Path
from urllib.parse import unquote
from gi.repository import GdkPixbuf, GLib
@@ -543,15 +544,17 @@ def copy_picon_reference(target, view, services, clipboard, transient):
show_dialog(DialogType.ERROR, transient, "No reference is present!")
def remove_all_unused_picons(settings, picons, services):
def remove_all_unused_picons(settings, services):
""" Removes picons from profile picons folder if there are no services for these picons. """
ids = {s.picon_id for s in services}
pcs = list(filter(lambda x: x not in ids, picons))
remove_picons(settings, pcs, picons)
for p in Path(settings.profile_picons_path).glob("*.png"):
if p.name not in ids and p.is_file():
p.unlink()
def remove_picons(settings, picon_ids, picons):
pions_path = settings.profile_picons_path
backup_path = "{}{}{}".format(settings.profile_backup_path, "picons", SEP)
backup_path = f"{settings.profile_backup_path}picons{SEP}"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
for p_id in picon_ids:
picons[p_id] = None

View File

@@ -730,6 +730,7 @@ Author: Dmitriy Yefremov
<property name="alignment">0.49000000953674316</property>
<child>
<object class="GtkCellRendererPixbuf" id="picons_src_renderer">
<property name="height">50</property>
<property name="ypad">5</property>
</object>
<attributes>
@@ -865,6 +866,7 @@ Author: Dmitriy Yefremov
<property name="alignment">0.49000000953674316</property>
<child>
<object class="GtkCellRendererPixbuf" id="picons_dest_renderer">
<property name="height">50</property>
<property name="ypad">5</property>
</object>
<attributes>

View File

@@ -549,6 +549,7 @@ class PiconManager(Gtk.Box):
filter_model = model.get_model()
itr = filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr))
base_model.remove(itr)
self._app.update_picons()
if view is self._picons_dest_view:
self._dst_count_label.set_text(str(len(model)))

View File

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

View File

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

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.0.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2021, Dmitriy Yefremov",
'CFBundleShortVersionString': f"2.1.2.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2022, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false'
})