diff --git a/src/simple_recorder/chapter.py b/src/simple_recorder/chapter.py new file mode 100644 index 0000000..5b16e2c --- /dev/null +++ b/src/simple_recorder/chapter.py @@ -0,0 +1,48 @@ +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 Chapter(Command): + """Create a chapter in the current recording.""" + + chapter_name: Positional[str] = arg( + help="Name of the chapter to create.", + prompt="Enter the name for the chapter.", + default="unnamed", + ) + host: str = arg(inherited=True) + port: int = arg(inherited=True) + password: str = arg(inherited=True) + + @override + async def run(self): + """Run the chapter command.""" + try: + with obsws.ReqClient( + host=self.host, port=self.port, password=self.password, timeout=3 + ) as client: + resp = client.get_record_status() + if not resp.output_active: + raise SimpleRecorderError( + "No active recording to create a chapter." + ) + + # Allow OBS to set unnamed chapters (it will increment the name) + if self.chapter_name == "unnamed": + client.create_record_chapter() + else: + client.create_record_chapter(self.chapter_name) + print(f"Chapter {highlight(self.chapter_name)} created successfully.") + except (ConnectionRefusedError, TimeoutError): + raise SimpleRecorderError("Failed to connect to OBS. Is it running?") + except obsws.error.OBSSDKRequestError as e: + if e.code == 702: + raise SimpleRecorderError( + "Unable to create chapter, please check your OBS settings." + ) + else: + raise SimpleRecorderError(f"Error: {e}") diff --git a/src/simple_recorder/cli.py b/src/simple_recorder/cli.py index 2a52ee9..cf3b1a7 100644 --- a/src/simple_recorder/cli.py +++ b/src/simple_recorder/cli.py @@ -3,6 +3,7 @@ import logging from clypi import ClypiConfig, ClypiException, Command, arg, configure from typing_extensions import override +from .chapter import Chapter from .directory import Directory from .errors import SimpleRecorderError from .gui import SimpleRecorderWindow @@ -12,8 +13,6 @@ from .split import Split from .start import Start from .stop import Stop -logger = logging.getLogger(__name__) - config = ClypiConfig( nice_errors=(SimpleRecorderError,), ) @@ -38,7 +37,7 @@ def theme_parser(value: str) -> str: return value -SUBCOMMANDS = Start | Stop | Pause | Resume | Split | Directory +SUBCOMMANDS = Start | Stop | Pause | Resume | Split | Chapter | Directory class SimpleRecorder(Command): @@ -61,11 +60,15 @@ class SimpleRecorder(Command): ) @override - async def run(self): - """Run the Simple Recorder GUI.""" + async def pre_run_hook(self): if self.debug: logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.disable()) + @override + async def run(self): + """Run the Simple Recorder GUI.""" window = SimpleRecorderWindow(self.host, self.port, self.password, self.theme) await window.run() diff --git a/src/simple_recorder/gui.py b/src/simple_recorder/gui.py index 60de96e..d115f32 100644 --- a/src/simple_recorder/gui.py +++ b/src/simple_recorder/gui.py @@ -3,6 +3,7 @@ import logging import FreeSimpleGUI as fsg import obsws_python as obsws +from .chapter import Chapter from .directory import Directory from .errors import SimpleRecorderError from .pause import Pause @@ -96,8 +97,9 @@ class SimpleRecorderWindow(fsg.Window): self["Pause Recording"].bind("", " || RETURN") self["Resume Recording"].bind("", " || RETURN") self["Split Recording"].bind("", " || RETURN") + self["Add Chapter"].bind("", " || RETURN") + self["Add Chapter"].bind("", " || SHIFT-RETURN") - self["-FILENAME-"].bind("", " || KEYPRESS") self["-FILENAME-"].update(select=True) self["Add Chapter"].bind("", " || FOCUS") self["Add Chapter"].bind("", " || FOCUS") @@ -109,6 +111,8 @@ class SimpleRecorderWindow(fsg.Window): self["-UPDATE-"].bind("", " || RETURN") async def run(self): + chapter_name = "unnamed" + while True: event, values = self.read() self.logger.debug(f"Event: {event}, Values: {values}") @@ -192,17 +196,32 @@ class SimpleRecorderWindow(fsg.Window): f"Error: {e.raw_message}", text_color="red" ) - case ["Add Chapter", "RIGHT_CLICK"]: - _ = fsg.popup_get_text( + case ["Add Chapter", "RIGHT_CLICK" | "SHIFT-RETURN"]: + chapter_name = fsg.popup_get_text( "Enter chapter name:", "Add Chapter", default_text="unnamed", ) - case ["Add Chapter"]: - self["-OUTPUT-RECORDER-"].update( - "This feature is not implemented yet", text_color="orange" - ) + case ["Add Chapter"] | ["Add Chapter", "RETURN"]: + try: + await Chapter( + chapter_name=chapter_name, + host=self.host, + port=self.port, + password=self.password, + ).run() + self["-OUTPUT-RECORDER-"].update( + f"Chapter {chapter_name if chapter_name else 'unnamed'} added successfully", + text_color="green", + ) + except SimpleRecorderError: + fsg.popup_error( + "Unable to create chapter, please check your OBS settings.\n" + "Note, currently only Hybrid MP4 is supported for chapters.", + title="Chapter Error", + keep_on_top=True, + ) case ["-GET-CURRENT-"] | ["-GET-CURRENT-", "RETURN"]: try: diff --git a/src/simple_recorder/split.py b/src/simple_recorder/split.py index 30df6e5..94feb0c 100644 --- a/src/simple_recorder/split.py +++ b/src/simple_recorder/split.py @@ -1,13 +1,9 @@ -import logging - import obsws_python as obsws from clypi import Command, arg from typing_extensions import override from .errors import SimpleRecorderError -logging.basicConfig(level=logging.disable()) - class Split(Command): """Split the current recording into a new file."""