]> skyeroc.xyz Git - obs-ctl/commitdiff
Add record path (and change layout)
authorsbkelley <sb24kelley@gmail.com>
Sat, 29 Nov 2025 18:33:34 +0000 (13:33 -0500)
committersbkelley <sb24kelley@gmail.com>
Sat, 29 Nov 2025 18:33:34 +0000 (13:33 -0500)
src/obs_ctl/__init__.py
src/obs_ctl/data/layout.tcss
src/obs_ctl/widget/__init__.py

index 8ea9005b692830d457a034e33187facb0ad91c03..a6d5492a02a8a025b4e8a459e6f1220d2fe5eb8b 100644 (file)
@@ -1,16 +1,17 @@
 from dataclasses import dataclass
 import importlib.resources
+from pathlib import Path
 
 import obsws_python as obs
 import obsws_python.error as obs_error
 from textual import on
 from textual.app import App
-from textual.containers import HorizontalGroup, VerticalGroup, Vertical, Horizontal, Container, Grid
-from textual.widgets import Footer, Header, Label, Placeholder
+from textual.containers import Vertical, Horizontal, Grid, ScrollableContainer, Container
+from textual.widgets import Footer, Header, Label, Placeholder, TabbedContent, TabPane, Markdown, Input
 from textual.css.query import NoMatches
 
 from . import log, data
-from .widget import SingleStatusControl, LabeledLabel, LabeledLabel
+from .widget import SingleStatusControl, LabeledLabel, OBSSettingsPane
 
 logger = log.getLogger(__name__)
 obs_req_client = obs.ReqClient()
@@ -33,6 +34,7 @@ class OBSStats:
     memory_usage_mb: float
     active_fps: float
     available_disk_space: float
+    record_directory: str
 
     @property
     def estimated_hours_remaining(self):
@@ -62,20 +64,30 @@ class OBSCtlApp(App):
         yield Header()
         yield Footer()
         logger.debug("Composing OBSCtlApp")
-        # yield Label("Controls", id="controls_label", classes="labels"),
-        with Grid(id="controls_container", classes="blocks"):
-            yield SingleStatusControl(label="Recording", id="record_control", classes="controls")
-            yield SingleStatusControl(label="Paused", on_label="Pause", off_label="Unpause", swap_red=True, id="pause_control", classes="controls")
-            yield SingleStatusControl(label="Streaming", id="stream_control", classes="controls")
-        with Grid(id="machine_container", classes="blocks"):
-            yield LabeledLabel("CPU Usage", "-.-%", id="cpu_usage", classes="stats")
-            yield LabeledLabel("RAM Usage", "-b", id="memory_usage", classes="stats")
-            yield LabeledLabel("Time 'til Full:", "--:--:--", id="disk_full", classes="stats")
-        with Grid(id="obs_container", classes="blocks"):
-            yield LabeledLabel("FPS", "--.--", id="fps", classes="stats")
-            yield LabeledLabel("Dropped frames (network):", "-/- (-.-%)", id="stream_dropped_frames", classes="stats")
-            yield LabeledLabel("Dropped frames (rendering)", "-/- (-.-%)", id="rendering_lag", classes="stats")
-            yield LabeledLabel("Dropped frames (encoding)", "-/- (-.-%)", id="encoding_lag", classes="stats")
+        with ScrollableContainer(id="content_container"):
+            with Vertical(id="blocks_v_container", classes="blocks_containers"):
+                with Horizontal(id="blocks_h_container1", classes="blocks_h_containers"):
+                    with Grid(id="controls_container", classes="blocks gridblocks"):
+                        yield SingleStatusControl(label="Recording", id="record_control", classes="controls")
+                        yield SingleStatusControl(label="Paused", on_label="Pause", off_label="Unpause", swap_red=True, id="pause_control", classes="controls")
+                        yield SingleStatusControl(label="Streaming", id="stream_control", classes="controls")
+                    with TabbedContent(id="settings_container", classes="blocks"):
+                        with TabPane("OBS Settings", id="settings-tab"):
+                            yield OBSSettingsPane(id="settings-content")
+                        with TabPane("OBS Connection", id="connection-tab"):
+                            yield Placeholder("CONNECTION BLOCK", id="conection-plc")
+                        with TabPane("obs-ctl Settings", id="clients-tab"):
+                            yield Placeholder("CLIENT SETTINGS BLOCK", id="client-plc")
+                with Horizontal(id="blocks_h_container2", classes="blocks_h_containers"):
+                    with Grid(id="machine_container", classes="blocks gridblocks"):
+                        yield LabeledLabel("CPU Usage", "-.-%", id="cpu_usage", classes="stats")
+                        yield LabeledLabel("RAM Usage", "-b", id="memory_usage", classes="stats")
+                        yield LabeledLabel("Time 'til Full", "N/A", id="disk_full", classes="stats")
+                    with Grid(id="obs_container", classes="blocks gridblocks"):
+                        yield LabeledLabel("FPS", "--.--", id="fps", classes="stats")
+                        yield LabeledLabel("Netwrk frames dropped", "-/- (-.-%)", id="stream_dropped_frames", classes="stats")
+                        yield LabeledLabel("Render frames dropped", "-/- (-.-%)", id="rendering_lag", classes="stats")
+                        yield LabeledLabel("Encdng frames dropped", "-/- (-.-%)", id="encoding_lag", classes="stats")
 
 
     @on(SingleStatusControl.TurnedOff, "#stream_control")
@@ -147,11 +159,26 @@ class OBSCtlApp(App):
                 raise
 
 
+    @on(OBSSettingsPane.RecordPathChanged, "#settings-content")
+    def set_record_path(self, message: OBSSettingsPane.RecordPathChanged):
+        logger.debug(f"Set record path rcv'd: {message.record_path}")
+        if Path(message.record_path).exists():
+            try:
+                obs_req_client.set_record_directory(message.record_path)
+            except obs_error.OBSSDKRequestError as err:
+                if err.code == 500:
+                    logger.debug("Recording is running.")
+                else:
+                    raise
+        else:
+            logger.debug("The path doesn't exist...")
+
     @staticmethod
     def get_obs_stats():
         record_status = obs_req_client.get_record_status()
         stream_status = obs_req_client.get_stream_status()
         obs_stats = obs_req_client.get_stats()
+        record_directory = obs_req_client.get_record_directory()
         # logger.debug(record_status)
         # logger.debug(stream_status)
         # logger.debug(obs_stats.attrs())
@@ -171,6 +198,7 @@ class OBSCtlApp(App):
             memory_usage_mb=obs_stats.memory_usage,
             active_fps=obs_stats.active_fps,
             available_disk_space=obs_stats.available_disk_space,
+            record_directory=record_directory.record_directory,
         )
 
         return output
@@ -187,6 +215,7 @@ class OBSCtlApp(App):
         stream_dropped_frames: LabeledLabel = self.query_exactly_one("#stream_dropped_frames")
         rendering_lag: LabeledLabel = self.query_exactly_one("#rendering_lag")
         encoding_lag: LabeledLabel = self.query_exactly_one("#encoding_lag")
+        obs_settings: OBSSettingsPane = self.query_exactly_one("#settings-content")
 
         stream_ctl.status = obs_stats.stream_output_active
         record_ctl.status = obs_stats.record_output_active
@@ -198,6 +227,8 @@ class OBSCtlApp(App):
         stream_dropped_frames.text = f"{obs_stats.stream_output_skipped_frames}/{obs_stats.stream_output_total_frames}"
         rendering_lag.text = f"{obs_stats.stats_render_skipped_frames}/{obs_stats.stats_render_total_frames}"
         encoding_lag.text = f"{obs_stats.stats_output_skipped_frames}/{obs_stats.stats_output_total_frames}"
+        obs_settings.record_path = obs_stats.record_directory
+
 
 
     def process_obs_stats(self):
index f724044601f1c6bdb7cc9b481b0bf13850bcc002..ef662b201f8b05f1178dcc64e3f6438cd9ba211c 100644 (file)
@@ -1,52 +1,56 @@
-Screen {
-    layout:grid;
-    grid-size:2 2;
-    background:$background;
+#content_container {
+    # overflow:scroll scroll;
+}
 
-    .blocks {
-        background:$background;
-        grid-size:2 2;
-        width:1fr;
-        border:solid $primary;
-        
-        SingleStatusControl {
-            background:$surface;
-            border:blank;
-            .singlestatuscontrol--light {
-                width:1fr;
-                height:1fr;
-                background:$panel;
-                text-align:center;
-                align:center bottom;
-            }
-            .singlestatuscontrol--on_button {
-                width:1fr;
-                height:1fr;
-            }
-            .singlestatuscontrol--off_button {
-                width:1fr;
-                height:1fr;
-            }
-        }
-        
-        LabeledLabel {
-            width:1fr;
-            border:solid $primary;
-            background:$surface;
-            .labeledlabel--constant {
-                width:1fr;
-                height:1;
-                align:center middle;
+#settings_container {
+    width:1fr;
+    height:1fr;
+    OBSSettingsPane {
+        # width:30;
+    }
+}
+
+#blocks_v_container {
+    overflow:auto auto;
+    background:$background;
+    .blocks_h_containers {
+        .blocks {
+            &.gridblocks {
+                layout:grid;
+                grid-size:2 2;
             }
-            .labeledlabel--variable {
-                align:center middle;
+            &#controls_container {
+                min-width:34;
+                grid-rows:5 1fr;
             }
         }
+    }
+}
 
-        &#controls_container {
-            width: 1fr;
-            column-span:2;
-        }
+#blocks_h_container1 {
+    min-height:10;
+}
 
+#blocks_h_container2 {
+    min-height:8;
+}
+
+SingleStatusControl {
+    background:$surface;
+    border:blank;
+    max-height:5;
+    align:center top;
+}
+
+LabeledLabel {
+    width:1fr;
+    border:solid $primary;
+    background:$surface;
+    .labeledlabel--constant {
+        height:1;
+        align:center middle;
+    }
+    .labeledlabel--variable {
+        align:center middle;
     }
 }
\ No newline at end of file
index 4403fc98700abe4c6f4522be6e25ec4436d88448..3d57701c9651514df8c8cf3a68e6b710dd2128f0 100644 (file)
@@ -1,8 +1,8 @@
 from textual.app import App, ComposeResult
-from textual.containers import Horizontal, Vertical, Grid, Container
+from textual.containers import Horizontal, VerticalScroll, Grid, Container
 from textual.message import Message
 from textual.reactive import reactive
-from textual.widgets import Label, RadioButton, Button
+from textual.widgets import Label, RadioButton, Button, Input
 from textual.widget import Widget
 from textual.css import query
 
@@ -11,7 +11,66 @@ from .. import log
 
 logger = log.getLogger(__name__)
 
+class OBSSettingsPane(VerticalScroll):
+    DEFAULT_CSS = """
+    .obssettingspane--input {
+        width:1fr;
+    }
+    Grid {
+        grid-size:2 1;
+        grid-columns:21 1fr;
+        Label {
+            width:1fr;
+            text-align:right;
+            padding-right:1;
+        }
+    }
+    """
+    COMPONENT_CLASSES = {
+        "obssettingspane--input",
+    }
+    record_path: reactive[str] = reactive("")
+    class RecordPathChanged(Message):
+        control: "OBSSettingsPane" = None
+        record_path: str = None
+        def __init__(self, ctrl, path):
+            super().__init__()
+            self.control = ctrl
+            self.record_path = path
+
+    def compose(self):
+        with Grid():
+            yield Label("Record path:")
+            yield Input("path", id="input_record_path", classes="obssettingspane--input", compact=True)
+    
+    def on_input_submitted(self, event:Input.Submitted):
+        self.set_reactive(OBSSettingsPane.record_path, event.value)
+        if event.control.id == "input_record_path":
+            self.post_message(OBSSettingsPane.RecordPathChanged(self, event.value))
+    
+    def watch_record_path(self, value):
+        input: Input = self.query_exactly_one("#input_record_path")
+        input.value = value
+
+
 class SingleStatusControl(Container):
+    DEFAULT_CSS = """
+        .singlestatuscontrol--light {
+            background:$panel;
+            text-align:center;
+            align:center bottom;
+            width:1fr;
+            height:1fr;
+        }
+        .singlestatuscontrol--on_button {
+            width:1fr;
+            height:1fr;
+        }
+        .singlestatuscontrol--off_button {
+            width:1fr;
+            height:1fr;
+        }
+    """
     COMPONENT_CLASSES = {
         "singlestatuscontrol--light",
         "singlesttauscontrol--on_button",