Forráskód Böngészése

optimizing ssync + general code cleanup + fix songchooser hotkeys

Noah Vogt 2 éve
szülő
commit
4d2fc7bb25

+ 17 - 2
README.md

@@ -330,11 +330,26 @@ which for example can look like this:
     2023-11-05
     3
 
-It then increments the value up to `OBS_MIN_SUBDIRS` and cycles back to 1. After each increment, it writes to the cachefile and sends a hotkey `Ctrl + Shift + F${value}` with the `$value` being the just incremented variable - which can be intercepted by OBS to change to the respecting scene for the song.
+It then increments the value up to `OBS_MIN_SUBDIRS` and cycles back to 1. After each increment, it writes to the cachefile and by default sends a hotkey `Ctrl + Shift + F${value}` with the `$value` being the just incremented variable - which can be intercepted by OBS to change to the respecting scene for the song. You can change the hotkey prefix, but not the last part with the function keys, for example the configuration to use `Ctrl + Alt + F${value}` would be
+
+```python
+OBS_SWITCH_TO_SCENE_HOTKEY_PREFIX = ["ctrl", "alt"]
+```
+
+To transition to the new song, it sends another hotkey defined by the following default:
+
+```python
+OBS_TRANSITION_HOTKEY = ["ctrl", "shift", "f12"]
+```
+
 
 ### force_song.py
 
-Instead of cycling like `next_song.py`, `force_song.py` takes an integer as single argument, sends the corresponding hotkey and saves the value to the same cachefile.
+Instead of cycling like `next_song.py`, `force_song.py` takes an integer as single argument, sends the corresponding hotkey and saves the value to the same cachefile. For example, use
+
+    ./force_song.py 4
+
+to switch to the scene with song 4.
 
 ## Roadmap
 

+ 4 - 0
config/default_config.py

@@ -87,3 +87,7 @@ SSYNC_CHOSEN_FILE_NAMING = ".chosen-file.txt"
 OBS_SLIDES_DIR = ""
 OBS_SUBDIR_NAMING = ""
 OBS_MIN_SUBDIRS = 7
+
+NEXTSONG_CACHE_FILE = ""
+OBS_SWITCH_TO_SCENE_HOTKEY_PREFIX = ["ctrl", "shift"]
+OBS_TRANSITION_HOTKEY = ["ctrl", "shift", "f12"]

+ 5 - 2
input/__init__.py

@@ -21,7 +21,10 @@ from .parse_file import (
     parse_songtext,
     get_songchooser_cachefile_content,
 )
-from .parse_argv import parse_argv_as_tuple
-from .parse_argv import parse_ssync_args_as_tuple
+from .parse_argv import (
+    parse_ssync_args_as_tuple,
+    parse_slidegen_argv_as_tuple,
+    SsyncFlags,
+)
 from .validate_config import validate_ssync_config, validate_songchooser_config
 from .slide_selection_iterator import slide_selection_iterator

+ 9 - 2
input/parse_argv.py

@@ -16,11 +16,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
 import argparse
+from dataclasses import dataclass
 
 from utils import log, error_msg, expand_dir
 
 
-def parse_argv_as_tuple() -> tuple:
+def parse_slidegen_argv_as_tuple() -> tuple:
     parser = argparse.ArgumentParser(
         prog="slidegen", description="slidegen - a slide generator."
     )
@@ -56,7 +57,7 @@ def parse_argv_as_tuple() -> tuple:
     except IndexError:
         chosen_structure = ""
 
-    log("parsing {}...".format(song_file_path))
+    log("parsing '{}'...".format(song_file_path))
     return song_file_path, output_dir, chosen_structure
 
 
@@ -77,3 +78,9 @@ def parse_ssync_args_as_tuple() -> tuple:
     )
     args = parser.parse_args()
     return args.offline, args.sequential
+
+
+@dataclass
+class SsyncFlags:
+    offline_enabled: bool
+    disable_async_enabled: bool

+ 1 - 1
input/parse_prompt.py

@@ -45,7 +45,7 @@ def generate_final_prompt(structure_prompt_answer, full_song_structure) -> str:
             "warning: prompt input '{}' is invalid, defaulting to full".format(
                 structure_prompt_answer
             )
-            + "song structure",
+            + " song structure...",
             color="cyan",
         )
         calculated_prompt = full_song_structure

+ 13 - 6
input/slide_selection_iterator.py

@@ -24,13 +24,16 @@ from utils import (
     expand_dir,
 )
 from input import parse_metadata, generate_final_prompt
+from slides import SlideStyle
 
 import config as const
 
 import slidegen
 
 
-def slide_selection_iterator(ssync):
+def slide_selection_iterator(
+    disable_async_enabled: bool, slide_style: SlideStyle
+) -> None:
     iterator_prompt = "Exit now? [y/N]: "
     structure_prompt = (
         "Choose song structure (leave blank for full song)"
@@ -67,7 +70,7 @@ def slide_selection_iterator(ssync):
             )
 
             full_song_structure = get_structure_for_prompt(
-                ssync.slide_style, src_dir, dest_dir
+                slide_style, src_dir, dest_dir
             )
             log(
                 "full song structure of '{}':\n{}".format(
@@ -88,20 +91,24 @@ def slide_selection_iterator(ssync):
             )
 
             generate_slides_for_selected_song(
-                ssync.slide_style,
+                slide_style,
                 src_dir,
                 dest_dir,
                 generate_final_prompt(
                     structure_prompt_answer, full_song_structure
                 ),
-                ssync,
+                disable_async_enabled,
             )
 
     remove_chosenfile()
 
 
 def generate_slides_for_selected_song(
-    classic_slide_style, src_dir, dest_dir, calculated_prompt, ssync
+    classic_slide_style: SlideStyle,
+    src_dir: str,
+    dest_dir: str,
+    calculated_prompt: str | list[str],
+    disable_async_enabled: bool,
 ) -> None:
     executing_slidegen_instance = slidegen.Slidegen(
         classic_slide_style,
@@ -109,7 +116,7 @@ def generate_slides_for_selected_song(
         dest_dir,
         calculated_prompt,
     )
-    executing_slidegen_instance.execute(ssync.disable_async)
+    executing_slidegen_instance.execute(disable_async_enabled)
 
 
 def get_structure_for_prompt(classic_slide_style, src_dir, dest_dir):

+ 2 - 0
input/validate_config.py

@@ -39,6 +39,8 @@ def validate_songchooser_config() -> None:
     needed_constants: dict = {
         "NEXTSONG_CACHE_FILE": const.NEXTSONG_CACHE_FILE,
         "OBS_MIN_SUBDIRS": const.OBS_MIN_SUBDIRS,
+        "OBS_TRANSITION_HOTKEY": const.OBS_TRANSITION_HOTKEY,
+        "OBS_SWITCH_TO_SCENE_HOTKEY_PREFIX": const.OBS_SWITCH_TO_SCENE_HOTKEY_PREFIX,
     }
     general_config_validator(needed_constants)
 

+ 3 - 3
slidegen.py

@@ -35,7 +35,7 @@ from input import (
     parse_prompt_input,
     parse_metadata,
     parse_songtext,
-    parse_argv_as_tuple,
+    parse_slidegen_argv_as_tuple,
 )
 
 
@@ -45,7 +45,7 @@ class Slidegen:
         slide_style: SlideStyle,
         song_file_path: str,
         output_dir: str,
-        chosen_structure: str | list,
+        chosen_structure: str | list[str],
     ) -> None:
         self.metadata: dict = {"": ""}
         self.songtext: dict = {"": ""}
@@ -86,7 +86,7 @@ def main() -> None:
         ClassicStartSlide,  # pyright: ignore [reportGeneralTypeIssues]
         ClassicSongSlide,  # pyright: ignore [reportGeneralTypeIssues]
     )
-    slidegen = Slidegen(classic_slide_style, *parse_argv_as_tuple())
+    slidegen = Slidegen(classic_slide_style, *parse_slidegen_argv_as_tuple())
     slidegen.execute()
 
 

+ 11 - 15
ssync.py

@@ -30,23 +30,18 @@ from input import (
     validate_ssync_config,
     slide_selection_iterator,
     parse_ssync_args_as_tuple,
+    SsyncFlags,
 )
 from sync import sync_slide_repo, save_new_checkfile, syncing_needed
 
 
-class Ssync:
-    def __init__(self, offline: bool, sequential: bool, slide_style) -> None:
-        validate_ssync_config()
-        self.offline_flag_enabled = offline
-        self.disable_async = sequential
-        self.slide_style = slide_style
-
-    def execute(self):
-        if syncing_needed(self):
-            sync_slide_repo()
-            save_new_checkfile()
-        clear_obs_slides_dir()
-        slide_selection_iterator(self)
+def ssync(ssync_flags: SsyncFlags, slide_style: SlideStyle) -> None:
+    validate_ssync_config()
+    if syncing_needed(ssync_flags.offline_enabled):
+        sync_slide_repo()
+        save_new_checkfile()
+    clear_obs_slides_dir()
+    slide_selection_iterator(ssync_flags.disable_async_enabled, slide_style)
 
 
 def main() -> None:
@@ -57,8 +52,9 @@ def main() -> None:
         ClassicStartSlide,  # pyright: ignore [reportGeneralTypeIssues]
         ClassicSongSlide,  # pyright: ignore [reportGeneralTypeIssues]
     )
-    ssync = Ssync(*parse_ssync_args_as_tuple(), slide_style=classic_slide_style)
-    ssync.execute()
+    ssync_flags = SsyncFlags(*parse_ssync_args_as_tuple())
+
+    ssync(ssync_flags, classic_slide_style)
 
 
 if __name__ == "__main__":

+ 2 - 2
sync/syncing_needed.py

@@ -23,8 +23,8 @@ from utils import log
 import config as const
 
 
-def syncing_needed(slidegen) -> bool:
-    if slidegen.offline_flag_enabled:
+def syncing_needed(offline_flag_enabled: bool) -> bool:
+    if offline_flag_enabled:
         log("skipping sync with remote", color="cyan")
         return False
 

+ 22 - 7
utils/songchooser.py

@@ -16,8 +16,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
 from os import path
+from time import sleep
 
-from pyautogui import hotkey
+from pyautogui import keyDown, keyUp
 
 from utils import error_msg, log, calculate_yyyy_mm_dd_date
 import config as const
@@ -38,12 +39,26 @@ def make_sure_cachefile_exists() -> None:
             )
 
 
-def switch_to_song(song: int) -> None:
-    if song > const.OBS_MIN_SUBDIRS:
-        song = 1
-    log("sending hotkey Ctr + Shift + F{}".format(song), color="cyan")
-    hotkey("ctrl", "shift", "f{}".format(song))
-    create_cachfile_for_song(song)
+def switch_to_song(song_number: int) -> None:
+    if song_number > const.OBS_MIN_SUBDIRS:
+        song_number = 1
+    log("sending hotkey to switch to scene {}".format(song_number), "cyan")
+    scene_switch_hotkey = list(const.OBS_SWITCH_TO_SCENE_HOTKEY_PREFIX)
+    scene_switch_hotkey.append("f{}".format(song_number))
+    safe_send_hotkey(scene_switch_hotkey)
+
+    log("sending hotkey to transition to scene {}".format(song_number), "cyan")
+    safe_send_hotkey(const.OBS_TRANSITION_HOTKEY)
+
+    create_cachfile_for_song(song_number)
+
+
+def safe_send_hotkey(hotkey: list, sleep_time=0.1) -> None:
+    for key in hotkey:
+        keyDown(key)
+    sleep(sleep_time)
+    for key in hotkey:
+        keyUp(key)
 
 
 def create_cachfile_for_song(song) -> None: