diff --git a/app/tools/media.py b/app/tools/media.py
index 9ef94dca..4ba89ec1 100644
--- a/app/tools/media.py
+++ b/app/tools/media.py
@@ -1,6 +1,8 @@
+import os
import sys
+from datetime import datetime
-from app.commons import run_task, log
+from app.commons import run_task, log, _DATE_FORMAT
class Player:
@@ -103,5 +105,61 @@ class Player:
self._player.set_fullscreen(full)
+class Recorder:
+ __VLC_REC_INSTANCE = None
+
+ _CMD = "sout=#std{{access=file,mux=ts,dst={}{}_{}.ts}}"
+
+ def __init__(self):
+ try:
+ from app.tools import vlc
+ from app.tools.vlc import EventType
+ except OSError as e:
+ log("{}: Load library error: {}".format(__class__.__name__, e))
+ raise ImportError
+ else:
+ self._is_record = False
+ args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
+ self._recorder = vlc.Instance(args).media_player_new()
+
+ @classmethod
+ def get_instance(cls):
+ if not cls.__VLC_REC_INSTANCE:
+ cls.__VLC_REC_INSTANCE = Recorder()
+ return cls.__VLC_REC_INSTANCE
+
+ @run_task
+ def record(self, url, path, name):
+ if self._recorder:
+ self._recorder.stop()
+
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ d_now = datetime.now().strftime(_DATE_FORMAT)
+ media = self._recorder.get_instance().media_new(url, self._CMD.format(path, name, d_now))
+ media.get_mrl()
+
+ self._recorder.set_media(media)
+ self._is_record = True
+ self._recorder.play()
+ log("Record started {}".format(d_now))
+
+ @run_task
+ def stop(self):
+ self._recorder.stop()
+ self._is_record = False
+ log("Recording stopped.")
+
+ def is_record(self):
+ return self._is_record
+
+ @run_task
+ def release(self):
+ if self._recorder:
+ self._recorder.stop()
+ self._recorder.release()
+ self._is_record = False
+ log("Recording stopped. Releasing...")
+
+
if __name__ == "__main__":
pass
diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py
index 6dbd6345..16d0d49b 100644
--- a/app/ui/main_app_window.py
+++ b/app/ui/main_app_window.py
@@ -17,7 +17,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
-from app.tools.media import Player
+from app.tools.media import Player, Recorder
from app.ui.epg_dialog import EpgDialog
from app.ui.transmitter import LinksTransmitter
from .backup import BackupDialog, backup_data, clear_data_path
@@ -69,6 +69,7 @@ class Application(Gtk.Application):
super().__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, **kwargs)
# Adding command line options
self.add_main_option("log", ord("l"), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, "", None)
+ self.add_main_option("record", ord("r"), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, "", None)
self._handlers = {"on_close_app": self.on_close_app,
"on_resize": self.on_resize,
@@ -172,6 +173,8 @@ class Application(Gtk.Application):
self._player = None
self._full_screen = False
self._drawing_area_xid = None
+ # Record
+ self._recorder = None
# http api
self._http_api = None
self._fav_click_mode = None
@@ -226,6 +229,9 @@ class Application(Gtk.Application):
self._tv_count_label = builder.get_object("tv_count_label")
self._radio_count_label = builder.get_object("radio_count_label")
self._data_count_label = builder.get_object("data_count_label")
+ self._app_info_box.bind_property("visible", self._save_header_button, "visible", 4)
+ self._signal_level_bar.bind_property("visible", builder.get_object("play_current_service_button"), "visible")
+ self._signal_level_bar.bind_property("visible", builder.get_object("record_button"), "visible")
self._receiver_info_box.bind_property("visible", self._http_status_image, "visible", 4)
self._receiver_info_box.bind_property("visible", self._signal_box, "visible")
# Force ctrl press event for view. Multiple selections in lists only with Space key(as in file managers)!!!
@@ -264,7 +270,8 @@ class Application(Gtk.Application):
self._player_box.bind_property("visible", self._profile_combo_box, "sensitive", 4)
self._fav_view.bind_property("sensitive", self._player_prev_button, "sensitive")
self._fav_view.bind_property("sensitive", self._player_next_button, "sensitive")
- self._signal_level_bar.bind_property("visible", builder.get_object("play_current_service_button"), "visible")
+ # 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")
@@ -395,9 +402,14 @@ class Application(Gtk.Application):
""" Processing command line parameters. """
options = command_line.get_options_dict()
options = options.end().unpack()
+
if "log" in options:
init_logger()
+ if "record" in options:
+ log("Starting record of current stream...")
+ log("Not implemented yet!")
+
self.activate()
return 0
@@ -474,9 +486,15 @@ class Application(Gtk.Application):
""" Function for force ctrl press event for view """
event.state |= MOD_MASK
- @run_idle
def on_close_app(self, *args):
- self.quit()
+ if self._recorder:
+ if self._recorder.is_record():
+ msg = "{}\n\n\t{}".format(get_message("Recording in progress!"), get_message("Are you sure?"))
+ if show_dialog(DialogType.QUESTION, self._main_window, msg) == Gtk.ResponseType.CANCEL:
+ return True
+ self._recorder.release()
+
+ GLib.idle_add(self.quit)
def on_resize(self, window):
""" Stores new size properties for app window after resize """
@@ -925,7 +943,7 @@ class Application(Gtk.Application):
""" Shows satellites editor dialog """
show_satellites_dialog(self._main_window, self._settings)
- def on_download(self, action, value):
+ def on_download(self, action=None, value=None):
DownloadDialog(transient=self._main_window,
settings=self._settings,
open_data_callback=self.open_data,
@@ -1843,6 +1861,41 @@ class Application(Gtk.Application):
self._player_tool_bar.set_visible(visible)
self._status_bar_box.set_visible(visible and not self._app_info_box.get_visible())
+ # ************************* Record ***************************** #
+
+ def on_record(self, button):
+ if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
+ return True
+
+ if not self._recorder:
+ try:
+ self._recorder = Recorder.get_instance()
+ except (ImportError, NameError, AttributeError):
+ self.show_error_dialog("No VLC is found. Check that it is installed!")
+ return
+
+ is_record = self._recorder.is_record()
+
+ if is_record:
+ self._recorder.stop()
+ else:
+ self._http_api.send(HttpRequestType.STREAM_CURRENT, None, self.record)
+
+ def record(self, m3u):
+ if m3u:
+ url = [s for s in m3u.split("\n") if not s.startswith("#")]
+ if url:
+ self._recorder.record(url[0], self._settings.records_path, self._service_name_label.get_text())
+ GLib.timeout_add_seconds(1, self.update_record_button, priority=GLib.PRIORITY_LOW)
+
+ def update_record_button(self):
+ is_rec = self._recorder.is_record()
+ if not is_rec:
+ self._record_image.set_visible(True)
+ else:
+ self._record_image.set_visible(not self._record_image.get_visible())
+ return is_rec
+
# ************************ HTTP API **************************** #
def init_http_api(self):
diff --git a/app/ui/main_window.glade b/app/ui/main_window.glade
index 18e55c88..0651ddee 100644
--- a/app/ui/main_window.glade
+++ b/app/ui/main_window.glade
@@ -1343,6 +1343,7 @@ Author: Dmitriy Yefremov
+
+
+
+ False
+ True
+ 2
@@ -2551,7 +2573,7 @@ Author: Dmitriy Yefremov
False
True
- 1
+ 3
@@ -2570,7 +2592,7 @@ Author: Dmitriy Yefremov
True
True
- 2
+ 4
@@ -2584,7 +2606,7 @@ Author: Dmitriy Yefremov
False
False
- 4
+ 5