From ae86785ba6b76c29b3b9cbaf2c44c34f379bcca9 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Thu, 26 Jun 2025 06:15:48 +0100 Subject: [PATCH] separate the cli commands into different modules this makes it easier to use them as components of both the cli and the gui. --- src/simple_recorder/app.py | 164 ----------------------------------- src/simple_recorder/cli.py | 57 ++++++++++++ src/simple_recorder/start.py | 46 ++++++++++ src/simple_recorder/stop.py | 26 ++++++ 4 files changed, 129 insertions(+), 164 deletions(-) delete mode 100644 src/simple_recorder/app.py create mode 100644 src/simple_recorder/cli.py create mode 100644 src/simple_recorder/start.py create mode 100644 src/simple_recorder/stop.py diff --git a/src/simple_recorder/app.py b/src/simple_recorder/app.py deleted file mode 100644 index 13ab668..0000000 --- a/src/simple_recorder/app.py +++ /dev/null @@ -1,164 +0,0 @@ -import logging -from datetime import datetime - -import clypi -import FreeSimpleGUI as fsg -import obsws_python as obsws -from clypi import ClypiConfig, ClypiException, Command, Positional, arg, configure -from typing_extensions import override - -logger = logging.getLogger(__name__) - -config = ClypiConfig( - nice_errors=(ClypiException,), -) -configure(config) - -highlight = clypi.Styler(fg="green") -error = clypi.Styler(fg="red", bold=True) - - -class Start(Command): - """Start recording.""" - - filename: Positional[str] = arg( - default="default_name", - help="Name of the recording", - prompt="Enter the name for the recording", - ) - host: str = arg(inherited=True) - port: int = arg(inherited=True) - password: str = arg(inherited=True) - - @staticmethod - def get_timestamp(): - return datetime.now().strftime("%Y-%m-%d %H-%M-%S") - - @override - async def run(self): - if not self.filename: - raise ClypiException("Recording name cannot be empty.") - - with obsws.ReqClient( - host=self.host, port=self.port, password=self.password - ) as client: - resp = client.get_record_status() - if resp.output_active: - raise ClypiException("Recording is already active.") - - filename = f"{self.filename} {self.get_timestamp()}" - client.set_profile_parameter( - "Output", - "FilenameFormatting", - filename, - ) - client.start_record() - print(f"Recording started with filename: {highlight(filename)}") - - -class Stop(Command): - """Stop recording.""" - - host: str = arg(inherited=True) - port: int = arg(inherited=True) - password: str = arg(inherited=True) - - @override - async def run(self): - with obsws.ReqClient( - host=self.host, port=self.port, password=self.password - ) as client: - resp = client.get_record_status() - if not resp.output_active: - raise ClypiException("Recording is not active.") - - client.stop_record() - print("Recording stopped successfully.") - - -def theme_parser(value: str) -> str: - """Parse the theme argument.""" - themes = [ - "Light Purple", - "Neutral Blue", - "Reds", - "Sandy Beach", - "Kayak", - "Light Blue 2", - "Dark Teal1", - ] - if value not in themes: - raise ClypiException( - f"Invalid theme: {value}. Available themes: {', '.join(themes)}" - ) - return value - - -class SimpleRecorder(Command): - subcommand: Start | Stop | None = None - host: str = arg(default="localhost", env="OBS_HOST") - port: int = arg(default=4455, env="OBS_PORT") - password: str | None = arg(default=None, env="OBS_PASSWORD") - theme: str = arg(default="Reds", parser=theme_parser, env="OBS_THEME") - - @override - async def run(self): - fsg.theme(self.theme) - - input_text = fsg.InputText("", key="-FILENAME-") - start_record_button = fsg.Button("Start Recording", key="Start Recording") - stop_record_button = fsg.Button("Stop Recording", key="Stop Recording") - - layout = [ - [fsg.Text("Enter recording filename:")], - [input_text], - [start_record_button, stop_record_button], - [fsg.Text("Status: Not started", key="-OUTPUT-")], - ] - window = fsg.Window("Simple Recorder", layout, finalize=True) - status_text = window["-OUTPUT-"] - input_text.bind("", "-ENTER-") - start_record_button.bind("", "-ENTER-") - stop_record_button.bind("", "-ENTER-") - - while True: - event, values = window.read() - logger.debug(f"Event: {event}, Values: {values}") - if event == fsg.WIN_CLOSED: - break - elif event in ( - "Start Recording", - "Start Recording-ENTER-", - "-FILENAME--ENTER-", - ): - try: - await Start( - filename=input_text.get(), - host=self.host, - port=self.port, - password=self.password, - ).run() - status_text.update("Status: Recording started", text_color="green") - except ClypiException as e: - status_text.update(str(e), text_color="red") - logger.error(f"Error starting recording: {e}") - elif event in ("Stop Recording", "Stop Recording-ENTER-"): - try: - await Stop( - host=self.host, - port=self.port, - password=self.password, - ).run() - status_text.update("Status: Recording stopped", text_color="green") - except ClypiException as e: - status_text.update(str(e), text_color="red") - logger.error(f"Error stopping recording: {e}") - - -def run(): - """Run the application.""" - SimpleRecorder.parse().start() - - -if __name__ == "__main__": - run() diff --git a/src/simple_recorder/cli.py b/src/simple_recorder/cli.py new file mode 100644 index 0000000..289bec7 --- /dev/null +++ b/src/simple_recorder/cli.py @@ -0,0 +1,57 @@ +import logging + +from clypi import ClypiConfig, ClypiException, Command, arg, configure +from typing_extensions import override + +from .errors import SimpleRecorderError +from .gui import SimpleRecorderWindow +from .start import Start +from .stop import Stop + +logger = logging.getLogger(__name__) + +config = ClypiConfig( + nice_errors=(SimpleRecorderError,), +) +configure(config) + + +def theme_parser(value: str) -> str: + """Parse the theme argument.""" + themes = [ + "Light Purple", + "Neutral Blue", + "Reds", + "Sandy Beach", + "Kayak", + "Light Blue 2", + "Dark Teal1", + ] + if value not in themes: + raise ClypiException( + f"Invalid theme: {value}. Available themes: {', '.join(themes)}" + ) + return value + + +class SimpleRecorder(Command): + subcommand: Start | Stop | None = None + host: str = arg(default="localhost", env="OBS_HOST", help="OBS WebSocket host") + port: int = arg(default=4455, env="OBS_PORT", help="OBS WebSocket port") + password: str | None = arg( + default=None, env="OBS_PASSWORD", help="OBS WebSocket password" + ) + theme: str = arg( + default="Reds", parser=theme_parser, env="OBS_THEME", help="OBS WebSocket theme" + ) + + @override + async def run(self): + """Run the Simple Recorder CLI.""" + window = SimpleRecorderWindow(self.host, self.port, self.password, self.theme) + await window.run() + + +def run(): + """Run the application.""" + SimpleRecorder.parse().start() diff --git a/src/simple_recorder/start.py b/src/simple_recorder/start.py new file mode 100644 index 0000000..ff797a9 --- /dev/null +++ b/src/simple_recorder/start.py @@ -0,0 +1,46 @@ +from datetime import datetime + +import obsws_python as obsws +from clypi import Command, Positional, arg +from typing_extensions import override + +from .errors import SimpleRecorderError +from .styler import highlight + + +class Start(Command): + """Start recording.""" + + filename: Positional[str] = arg( + default="default_name", + help="Name of the recording", + prompt="Enter the name for the recording", + ) + host: str = arg(inherited=True) + port: int = arg(inherited=True) + password: str = arg(inherited=True) + + @staticmethod + def get_timestamp(): + return datetime.now().strftime("%Y-%m-%d %H-%M-%S") + + @override + async def run(self): + if not self.filename: + raise SimpleRecorderError("Recording name cannot be empty.") + + with obsws.ReqClient( + host=self.host, port=self.port, password=self.password + ) as client: + resp = client.get_record_status() + if resp.output_active: + raise SimpleRecorderError("Recording is already active.") + + filename = f"{self.filename} {self.get_timestamp()}" + client.set_profile_parameter( + "Output", + "FilenameFormatting", + filename, + ) + client.start_record() + print(f"Recording started with filename: {highlight(filename)}") diff --git a/src/simple_recorder/stop.py b/src/simple_recorder/stop.py new file mode 100644 index 0000000..66efcde --- /dev/null +++ b/src/simple_recorder/stop.py @@ -0,0 +1,26 @@ +import obsws_python as obsws +from clypi import Command, arg +from typing_extensions import override + +from .errors import SimpleRecorderError +from .styler import highlight + + +class Stop(Command): + """Stop recording.""" + + host: str = arg(inherited=True) + port: int = arg(inherited=True) + password: str = arg(inherited=True) + + @override + async def run(self): + with obsws.ReqClient( + host=self.host, port=self.port, password=self.password + ) as client: + resp = client.get_record_status() + if not resp.output_active: + raise SimpleRecorderError("Recording is not active.") + + client.stop_record() + print(highlight("Recording stopped successfully."))