Browse Source

add force_song.py script + update README

Noah Vogt 2 years ago
parent
commit
ec69b03c8a
10 changed files with 248 additions and 122 deletions
  1. 20 1
      README.md
  2. 56 0
      force_song.py
  3. 6 2
      input/__init__.py
  4. 15 0
      input/parse_file.py
  5. 13 1
      input/validate_config.py
  6. 0 118
      next_slide.py
  7. 52 0
      next_song.py
  8. 2 0
      utils/__init__.py
  9. 22 0
      utils/date.py
  10. 62 0
      utils/songchooser.py

+ 20 - 1
README.md

@@ -17,6 +17,26 @@ Both of these processes have *major downsides*: They are hard to automate, take
 
 The only upside they have is that can be more intuitive for inexperienced computer users, but changing a text file template and uploading to a remote storage should not be too hard to manage and worth it as it has *none* of the above mentioned downsides.
 
+## Extra Scripts
+
+### next_song.py
+
+`next_song.py` checks which song was played last in a cachefile stored at `NEXTSONG_CACHE_FILE` of the form
+
+    YYYY-MM-DD
+    [0-9]+
+
+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.
+
+### 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.
+
 ## Usage
 
 ### Commandline Interface
@@ -311,7 +331,6 @@ These are some issues and possible changes that will be addressed or at least co
 - add more documentation, especially explaining the slide generation, but also dependencies and deployment
 - add tests
 - use smarter multi slide splitter algorithm: either by pattern recognition like line matching or rhymes of the last word or by incorporating some sort of sub-song-structures in the body.
-- add docs for extra scripts
 
 ## Licensing
 

+ 56 - 0
force_song.py

@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+"""
+Copyright © 2023 Noah Vogt <noah@noahvogt.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from sys import argv
+
+
+from utils import (
+    error_msg,
+    make_sure_cachefile_exists,
+    switch_to_song,
+)
+from input import validate_songchooser_config
+import config as const
+
+
+# pylint: disable=inconsistent-return-statements
+def get_force_int() -> int:
+    try:
+        return int(argv[1])
+    except IndexError:
+        error_msg("couldn't parse force song integer")
+
+
+def exit_if_force_int_is_illegal():
+    force_int = get_force_int()
+    if force_int > const.OBS_MIN_SUBDIRS:
+        error_msg("force integer too big")
+    if force_int < 1:
+        error_msg("force integer cannot be smaller than 1")
+
+
+def main() -> None:
+    validate_songchooser_config()
+    make_sure_cachefile_exists()
+    exit_if_force_int_is_illegal()
+    switch_to_song(get_force_int())
+
+
+if __name__ == "__main__":
+    main()

+ 6 - 2
input/__init__.py

@@ -16,8 +16,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
 from .parse_prompt import parse_prompt_input
-from .parse_file import parse_metadata, parse_songtext
+from .parse_file import (
+    parse_metadata,
+    parse_songtext,
+    get_songchooser_cachefile_content,
+)
 from .parse_argv import parse_argv_as_tuple
 from .parse_argv import parse_ssync_args
-from .validate_ssync_config import validate_ssync_config
+from .validate_config import validate_ssync_config, validate_songchooser_config
 from .slide_selection_iterator import slide_selection_iterator

+ 15 - 0
input/parse_file.py

@@ -86,3 +86,18 @@ def parse_songtext(slidegen) -> None:
         )
 
     slidegen.songtext = output_dict
+
+
+def get_songchooser_cachefile_content() -> list:
+    try:
+        with open(
+            const.NEXTSONG_CACHE_FILE, mode="r", encoding="utf8"
+        ) as cachefile_reader:
+            cachefile_content = cachefile_reader.readlines()
+    except (FileNotFoundError, PermissionError, IOError) as error:
+        error_msg(
+            "Failed to access cachefile in '{}'. Reason: {}".format(
+                const.NEXTSONG_CACHE_FILE, error
+            )
+        )
+    return cachefile_content

+ 13 - 1
input/validate_ssync_config.py → input/validate_config.py

@@ -20,7 +20,7 @@ from utils import log, error_msg
 import config as const
 
 
-def validate_ssync_config():
+def validate_ssync_config() -> None:
     needed_constants: dict = {
         "RCLONE_LOCAL_DIR": const.RCLONE_LOCAL_DIR,
         "RCLONE_REMOTE_DIR": const.RCLONE_REMOTE_DIR,
@@ -31,6 +31,18 @@ def validate_ssync_config():
         "OBS_SUBDIR_NAMING": const.OBS_SUBDIR_NAMING,
         "OBS_MIN_SUBDIRS": const.OBS_MIN_SUBDIRS,
     }
+    general_config_validator(needed_constants)
+
+
+def validate_songchooser_config() -> None:
+    needed_constants: dict = {
+        "NEXTSONG_CACHE_FILE": const.NEXTSONG_CACHE_FILE,
+        "OBS_MIN_SUBDIRS": const.OBS_MIN_SUBDIRS,
+    }
+    general_config_validator(needed_constants)
+
+
+def general_config_validator(needed_constants: dict) -> None:
     for key in needed_constants:
         if needed_constants.get(key) == "":
             error_msg("needed config entry '{}' is empty".format(key))

+ 0 - 118
next_slide.py

@@ -1,118 +0,0 @@
-#!/usr/bin/env python3
-
-"""
-Copyright © 2023 Noah Vogt <noah@noahvogt.com>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-from datetime import date
-from re import match
-from os import path
-
-from pyautogui import hotkey
-
-from utils import log, error_msg
-import config as const
-
-
-def calculate_date() -> str:
-    return date.strftime(date.today(), "%Y-%m-%d")
-
-
-def get_cachefile_content() -> list:
-    try:
-        with open(
-            const.NEXTSONG_CACHE_FILE, mode="r", encoding="utf8"
-        ) as cachefile_reader:
-            cachefile_content = cachefile_reader.readlines()
-    except (FileNotFoundError, PermissionError, IOError) as error:
-        error_msg(
-            "Failed to access cachefile in '{}'. Reason: {}".format(
-                const.NEXTSONG_CACHE_FILE, error
-            )
-        )
-    return cachefile_content
-
-
-def cycle_to_next_slide() -> None:
-    cachefile_content = get_cachefile_content()
-    if (
-        not (
-            len(cachefile_content) == 2
-            and match(r"[0-9]{4}-[0-9]{2}-[0-9]{2}$", cachefile_content[0])
-            and match(r"^[0-9]*$", cachefile_content[1])
-        )
-        or cachefile_content[0].strip() != calculate_date()
-    ):
-        switch_to_slide(1)
-    else:
-        switch_to_slide(int(cachefile_content[1]) + 1)
-
-
-def switch_to_slide(slide: int) -> None:
-    if slide > const.OBS_MIN_SUBDIRS:
-        slide = 1
-    log("sending hotkey Ctr + Shift + F{}".format(slide))
-    hotkey("ctrl", "shift", "f{}".format(slide))
-    create_cachfile_for_slide(slide)
-
-
-def create_cachfile_for_slide(slide) -> None:
-    log("writing slide {} cachefile...".format(slide))
-    try:
-        with open(
-            const.NEXTSONG_CACHE_FILE, mode="w", encoding="utf8"
-        ) as file_writer:
-            file_writer.write(calculate_date() + "\n")
-            file_writer.write(str(slide) + "\n")
-    except (FileNotFoundError, PermissionError, IOError) as error:
-        error_msg(
-            "Failed to write to cachefile '{}'. Reason: {}".format(
-                const.NEXTSONG_CACHE_FILE, error
-            )
-        )
-
-
-def validate_config() -> None:
-    if const.NEXTSONG_CACHE_FILE == "":
-        error_msg("needed config entry 'NEXTSONG_CACHE_FILE' is empty")
-    if const.OBS_MIN_SUBDIRS == "":
-        error_msg("needed config entry 'OBS_MIN_SUBDIRS' is empty")
-    log("configuration initialised")
-
-
-def make_sure_cachefile_exists() -> None:
-    if not path.isfile(const.NEXTSONG_CACHE_FILE):
-        try:
-            with open(
-                const.NEXTSONG_CACHE_FILE, mode="w+", encoding="utf8"
-            ) as file_creator:
-                file_creator.write("")
-        except (FileNotFoundError, PermissionError, IOError) as error:
-            error_msg(
-                "Failed to create cachefile in '{}'. Reason: {}".format(
-                    const.NEXTSONG_CACHE_FILE, error
-                )
-            )
-
-
-def main() -> None:
-    validate_config()
-    make_sure_cachefile_exists()
-    cycle_to_next_slide()
-
-
-if __name__ == "__main__":
-    main()

+ 52 - 0
next_song.py

@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+"""
+Copyright © 2023 Noah Vogt <noah@noahvogt.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from re import match
+
+from utils import (
+    calculate_yyyy_mm_dd_date,
+    switch_to_song,
+    make_sure_cachefile_exists,
+)
+from input import get_songchooser_cachefile_content, validate_songchooser_config
+
+
+def cycle_to_next_song() -> None:
+    cachefile_content = get_songchooser_cachefile_content()
+    if (
+        not (
+            len(cachefile_content) == 2
+            and match(r"[0-9]{4}-[0-9]{2}-[0-9]{2}$", cachefile_content[0])
+            and match(r"^[0-9]+$", cachefile_content[1])
+        )
+        or cachefile_content[0].strip() != calculate_yyyy_mm_dd_date()
+    ):
+        switch_to_song(1)
+    else:
+        switch_to_song(int(cachefile_content[1]) + 1)
+
+
+def main() -> None:
+    validate_songchooser_config()
+    make_sure_cachefile_exists()
+    cycle_to_next_song()
+
+
+if __name__ == "__main__":
+    main()

+ 2 - 0
utils/__init__.py

@@ -26,3 +26,5 @@ from .img import get_empty_image
 from .create_min_obs_subdirs import create_min_obs_subdirs
 from .clear_obs_slides_dir import clear_obs_slides_dir
 from .path import expand_dir
+from .date import calculate_yyyy_mm_dd_date
+from .songchooser import make_sure_cachefile_exists, switch_to_song

+ 22 - 0
utils/date.py

@@ -0,0 +1,22 @@
+"""
+Copyright © 2022 Noah Vogt <noah@noahvogt.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from datetime import date
+
+
+def calculate_yyyy_mm_dd_date() -> str:
+    return date.strftime(date.today(), "%Y-%m-%d")

+ 62 - 0
utils/songchooser.py

@@ -0,0 +1,62 @@
+"""
+Copyright © 2022 Noah Vogt <noah@noahvogt.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from os import path
+
+from pyautogui import hotkey
+
+from utils import error_msg, log, calculate_yyyy_mm_dd_date
+import config as const
+
+
+def make_sure_cachefile_exists() -> None:
+    if not path.isfile(const.NEXTSONG_CACHE_FILE):
+        try:
+            with open(
+                const.NEXTSONG_CACHE_FILE, mode="w+", encoding="utf8"
+            ) as file_creator:
+                file_creator.write("")
+        except (FileNotFoundError, PermissionError, IOError) as error:
+            error_msg(
+                "Failed to create cachefile in '{}'. Reason: {}".format(
+                    const.NEXTSONG_CACHE_FILE, error
+                )
+            )
+
+
+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 create_cachfile_for_song(song) -> None:
+    log("writing song {} to cachefile...".format(song))
+    try:
+        with open(
+            const.NEXTSONG_CACHE_FILE, mode="w", encoding="utf8"
+        ) as file_writer:
+            file_writer.write(calculate_yyyy_mm_dd_date() + "\n")
+            file_writer.write(str(song) + "\n")
+    except (FileNotFoundError, PermissionError, IOError) as error:
+        error_msg(
+            "Failed to write to cachefile '{}'. Reason: {}".format(
+                const.NEXTSONG_CACHE_FILE, error
+            )
+        )