diff --git a/ks_includes/config.py b/ks_includes/config.py index b3dd72e2..14b9a8a3 100644 --- a/ks_includes/config.py +++ b/ks_includes/config.py @@ -173,7 +173,7 @@ class KlipperScreenConfig: strs = ( 'moonraker_api_key', 'moonraker_host', 'titlebar_name_type', 'screw_positions', 'power_devices', 'titlebar_items', 'z_babystep_values', - 'extrude_distances', "extrude_speeds", "camera_url", + 'extrude_distances', "extrude_speeds", ) numbers = ( 'moonraker_port', 'move_speed_xy', 'move_speed_z', @@ -199,6 +199,9 @@ class KlipperScreenConfig: for key in config[section]: if key not in bools and key not in strs and key not in numbers: msg = f'Option "{key}" not recognized for section "[{section}]"' + if key == "camera_url": + msg = "camera_url has been deprecated in favor of moonraker cameras" + msg += "\n\n https://moonraker.readthedocs.io/en/latest/configuration/#webcam" if remove: # This should only be called for the auto-generated section self.config.remove_option(section, key) diff --git a/ks_includes/defaults.conf b/ks_includes/defaults.conf index 5d4c27cf..cfb1407a 100644 --- a/ks_includes/defaults.conf +++ b/ks_includes/defaults.conf @@ -147,7 +147,7 @@ enable: {{ printer.power_devices.count > 0 }} name: {{ gettext('Camera') }} icon: camera panel: camera -enable: {{ camera_configured }} +enable: {{ printer.cameras.count > 0 }} [menu __main more console] name: {{ gettext('Console') }} diff --git a/ks_includes/printer.py b/ks_includes/printer.py index 0dabb6a3..ae42019b 100644 --- a/ks_includes/printer.py +++ b/ks_includes/printer.py @@ -24,6 +24,7 @@ class Printer: self.busy_cb = busy_cb self.busy = False self.tempstore_size = 1200 + self.cameras = [] def reinit(self, printer_info, data): self.config = data['configfile']['config'] @@ -160,6 +161,10 @@ class Printer: } logging.debug(f"Power devices: {self.power_devices}") + def configure_cameras(self, data): + self.cameras = data + logging.debug(f"Cameras: {self.cameras}") + def get_config_section_list(self, search=""): if self.config is not None: return [i for i in list(self.config) if i.startswith(search)] if hasattr(self, "config") else [] @@ -228,6 +233,7 @@ class Printer: "idle_timeout": self.get_stat("idle_timeout").copy(), "pause_resume": {"is_paused": self.state == "paused"}, "power_devices": {"count": len(self.get_power_devices())}, + "cameras": {"count": len(self.cameras)}, } } diff --git a/panels/camera.py b/panels/camera.py index 843b7f4a..9db0c923 100644 --- a/panels/camera.py +++ b/panels/camera.py @@ -12,35 +12,53 @@ class Panel(ScreenPanel): def __init__(self, screen, title): super().__init__(screen, title) self.mpv = None - self.da = Gtk.DrawingArea() - self.da.set_hexpand(True) - self.da.set_vexpand(True) - fs = self._gtk.Button("move", _("Fullscreen"), None, self.bts, Gtk.PositionType.LEFT, 1) - fs.connect("clicked", self.play) - fs.set_hexpand(True) - fs.set_vexpand(False) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - box.add(self.da) - box.add(fs) - self.content.add(box) + for i, cam in enumerate(self._printer.cameras): + if not cam["enabled"]: + continue + logging.info(cam) + cam[cam["name"]] = self._gtk.Button( + image_name="camera", label=cam["name"], style=f"color{i % 4 + 1}", + scale=self.bts, position=Gtk.PositionType.LEFT, lines=1 + ) + cam[cam["name"]].set_hexpand(True) + cam[cam["name"]].set_vexpand(True) + cam[cam["name"]].connect("clicked", self.play, cam) + box.add(cam[cam["name"]]) + + self.scroll = self._gtk.ScrolledWindow() + self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.scroll.add(box) + self.content.add(self.scroll) self.content.show_all() - self.url = self.ks_printer_cfg.get("camera_url", "http://127.0.0.1/webcam/?action=stream").replace('"', '') - logging.debug(f"Camera URL: {self.url}") def activate(self): - self.play() + # if only 1 cam start playing fullscreen + if len(self._printer.cameras) == 1: + cam = next(iter(self._printer.cameras)) + if cam['enabled']: + self.play(None, cam) def deactivate(self): if self.mpv: self.mpv.terminate() self.mpv = None - def play(self, fs=None): + def play(self, widget, cam): + url = cam['stream_url'] + vf = "" + if cam["flip_horizontal"]: + vf += "hflip," + if cam["flip_vertical"]: + vf += "vflip," + vf += f"rotate:{cam['rotation']*3.14159/180}" + logging.info(f"video filters: {vf}") + if self.mpv: self.mpv.terminate() - self.mpv = None - # Create mpv after show or the 'window' property will be None - self.mpv = mpv.MPV(log_handler=self.log, vo='gpu,wlshm,xv,x11') + self.mpv = mpv.MPV(fullscreen=True, log_handler=self.log, vo='gpu,wlshm,xv,x11') + + self.mpv.vf = vf with suppress(Exception): self.mpv.profile = 'sw-fast' @@ -51,22 +69,13 @@ class Panel(ScreenPanel): self.mpv.untimed = True self.mpv.audio = 'no' - # On wayland mpv cannot be embedded at least for now - # https://github.com/mpv-player/mpv/issues/9654 - # if fs: - self.mpv.fullscreen = True - @self.mpv.on_key_press('MBTN_LEFT' or 'MBTN_LEFT_DBL') def clicked(): self.mpv.quit(0) - # else: - # self.mpv.wid = f'{self.da.get_property("window").get_xid()}' - # - # @self.mpv.on_key_press('MBTN_LEFT' or 'MBTN_LEFT_DBL') - # def clicked(): - # self._screen.show_popup_message(self.url, level=1) - self.mpv.play(self.url) - # if fs: + + logging.debug(f"Camera URL: {url}") + self.mpv.play(url) + try: self.mpv.wait_for_playback() except mpv.ShutdownError: @@ -75,8 +84,10 @@ class Panel(ScreenPanel): logging.exception(e) self.mpv.terminate() self.mpv = None - self._screen._menu_go_back() + if len(self._printer.cameras) == 1: + self._screen._menu_go_back() - @staticmethod - def log(loglevel, component, message): + def log(self, loglevel, component, message): logging.debug(f'[{loglevel}] {component}: {message}') + if loglevel == 'error': + self._screen.show_popup_message(f'{message}') diff --git a/panels/menu.py b/panels/menu.py index 9ff14f5c..367776f8 100644 --- a/panels/menu.py +++ b/panels/menu.py @@ -103,8 +103,6 @@ class Panel(ScreenPanel): if enable == "{{ moonraker_connected }}": logging.info(f"moonraker connected {self._screen._ws.connected}") return self._screen._ws.connected - elif enable == "{{ camera_configured }}": - return self.ks_printer_cfg and self.ks_printer_cfg.get("camera_url", None) is not None self.j2_data = self._printer.get_printer_status_data() try: j2_temp = Template(enable, autoescape=True) diff --git a/screen.py b/screen.py index a50dd06a..0f6ae63f 100755 --- a/screen.py +++ b/screen.py @@ -832,9 +832,16 @@ class KlipperScreen(Gtk.Window): # Moonraker is ready, set a loop to init the printer self.reinit_count += 1 - powerdevs = self.apiclient.send_request("machine/device_power/devices") - if powerdevs is not False: - self.printer.configure_power_devices(powerdevs['result']) + server_info = self.apiclient.get_server_info()["result"] + logging.info(f"Moonraker info {server_info}") + if "power" in server_info["components"]: + powerdevs = self.apiclient.send_request("machine/device_power/devices") + if powerdevs is not False: + self.printer.configure_power_devices(powerdevs['result']) + if "webcam" in server_info["components"]: + cameras = self.apiclient.send_request("server/webcams/list") + if cameras is not False: + self.printer.configure_cameras(cameras['result']['webcams']) if state['result']['klippy_connected'] is False: logging.info("Klipper not connected")