浏览代码

add initial ssync integration

Noah Vogt 2 年之前
父节点
当前提交
d1f270e8fd

+ 11 - 0
config/default_config.py

@@ -75,3 +75,14 @@ ARROW_X = 1725
 ARROW_Y = 900
 
 METADATA_STRINGS = ("title", "book", "text", "melody", "structure")
+
+RCLONE_REMOTE_DIR = ""
+RCLONE_LOCAL_DIR = ""
+
+SSYNC_CACHE_DIR = ""
+SSYNC_CHECKFILE_NAMING = "slidegen-checkfile.txt"
+SSYNC_CACHEFILE_NAMING = "slidegen-cachefile.txt"
+
+OBS_SLIDES_DIR = ""
+OBS_TARGET_SUBDIR = ""
+OBS_MIN_SUBDIRS = 7

+ 3 - 1
input/__init__.py

@@ -17,4 +17,6 @@ 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_argv import parse_argv
+from .parse_argv import parse_argv_as_tuple
+from .validate_ssync_config import validate_ssync_config
+from .slide_selection_iterator import slide_selection_iterator

+ 9 - 8
input/parse_argv.py

@@ -20,17 +20,18 @@ import sys
 from utils import log, error_msg
 
 
-def parse_argv(slidegen) -> None:
+def parse_argv_as_tuple() -> tuple:
     try:
-        slidegen.song_file_path = sys.argv[1]
-        slidegen.output_dir = sys.argv[2]
+        song_file_path = sys.argv[1]
+        output_dir = sys.argv[2]
     except IndexError:
         error_msg("incorrect amount of arguments provided, exiting...")
     try:
-        slidegen.chosen_structure = sys.argv[3]
-        if slidegen.chosen_structure.strip() == "":
-            slidegen.chosen_structure = ""
+        chosen_structure = sys.argv[3]
+        if chosen_structure.strip() == "":
+            chosen_structure = ""
     except IndexError:
-        slidegen.chosen_structure = ""
+        chosen_structure = ""
 
-    log("parsing {}...".format(slidegen.song_file_path))
+    log("parsing {}...".format(song_file_path))
+    return song_file_path, output_dir, chosen_structure

+ 87 - 0
input/slide_selection_iterator.py

@@ -0,0 +1,87 @@
+"""
+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/>.
+"""
+
+import os
+
+from utils import log, create_min_obs_subdirs
+from slides import ClassicSongTemplate, ClassicStartSlide, ClassicSongSlide
+
+import config as const
+
+import slidegen
+
+
+def slide_selection_iterator():
+    iterator_prompt = "Exit now? [y/N]: "
+    structure_prompt = (
+        "Choose song structure (leave blank for full song)"
+        + " eg. [1,R,2,R] / [1-4]: "
+    )
+    file_list_str = ""
+    for file in os.listdir(const.RCLONE_LOCAL_DIR):
+        file_list_str += file + "\n"
+    file_list_str = file_list_str[:-1]
+    tempfile_str = ".chosen-tempfile"
+
+    index = 0
+    while True:
+        index += 1
+        input_song_prompt = "[{} {}] ".format(const.OBS_TARGET_SUBDIR, index)
+        prompt_answer = str(input(input_song_prompt + iterator_prompt))
+        if prompt_answer.lower() == "y":
+            create_min_obs_subdirs(index)
+            break
+
+        file_list_str = file_list_str.replace("\n", "\\n")
+        os.system('printf "{}" | fzf > {}'.format(file_list_str, tempfile_str))
+
+        with open(
+            tempfile_str, encoding="utf-8", mode="r"
+        ) as tempfile_file_opener:
+            chosen_song_file = tempfile_file_opener.read()[:-1].strip()
+
+        if len(chosen_song_file) == 0:
+            log("no slides chosen, skipping...")
+        else:
+            structure_prompt_answer = input(
+                input_song_prompt + structure_prompt
+            )
+
+            log(
+                "generating slides '{}' to '{} {}'...".format(
+                    chosen_song_file, const.OBS_TARGET_SUBDIR, index
+                )
+            )
+            src_dir = os.path.join(const.RCLONE_LOCAL_DIR, chosen_song_file)
+            dest_dir = os.path.join(
+                const.SSYNC_CACHE_DIR,
+                const.OBS_TARGET_SUBDIR + " " + str(index),
+            )
+            os.mkdir(dest_dir)
+
+            slidegen_instance = slidegen.Slidegen(
+                ClassicSongTemplate,
+                ClassicStartSlide,
+                ClassicSongSlide,
+                src_dir,
+                dest_dir,
+                structure_prompt_answer,
+            )
+            slidegen_instance.execute()
+
+    if os.path.isfile(tempfile_str):
+        os.remove(tempfile_str)

+ 37 - 0
input/validate_ssync_config.py

@@ -0,0 +1,37 @@
+"""
+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 utils import log, error_msg
+
+import config as const
+
+
+def validate_ssync_config():
+    needed_constants: dict = {
+        "RCLONE_LOCAL_DIR": const.RCLONE_LOCAL_DIR,
+        "RCLONE_REMOTE_DIR": const.RCLONE_REMOTE_DIR,
+        "SSYNC_CHECKFILE_NAMING": const.SSYNC_CHECKFILE_NAMING,
+        "SSYNC_CACHEFILE_NAMING": const.SSYNC_CACHEFILE_NAMING,
+        "SSYNC_CACHE_DIR": const.SSYNC_CACHE_DIR,
+        "OBS_SLIDES_DIR": const.OBS_SLIDES_DIR,
+        "OBS_TARGET_SUBDIR": const.OBS_TARGET_SUBDIR,
+        "OBS_MIN_SUBDIRS": const.OBS_MIN_SUBDIRS,
+    }
+    for key in needed_constants:
+        if needed_constants.get(key) == "":
+            error_msg("needed config entry '{}' is empty".format(key))
+    log("configuration initialised")

+ 20 - 7
slidegen.py

@@ -31,24 +31,34 @@ from slides import (
     count_number_of_slides_to_be_generated,
 )
 
-from input import parse_prompt_input, parse_metadata, parse_songtext, parse_argv
+from input import (
+    parse_prompt_input,
+    parse_metadata,
+    parse_songtext,
+    parse_argv_as_tuple,
+)
 
 
 class Slidegen:
     def __init__(
-        self, song_template_form, start_slide_form, song_slide_form
+        self,
+        song_template_form,
+        start_slide_form,
+        song_slide_form,
+        song_file_path,
+        output_dir,
+        chosen_structure,
     ) -> None:
         self.metadata: dict = {"": ""}
         self.songtext: dict = {"": ""}
-        self.song_file_path: str = ""
+        self.song_file_path: str = song_file_path
         self.song_file_content: list = []
-        self.output_dir: str = ""
-        self.chosen_structure: list | str = ""
+        self.output_dir: str = output_dir
+        self.chosen_structure = chosen_structure
         self.generated_slides: list = []
         self.song_template_form = song_template_form
         self.start_slide_form = start_slide_form
         self.song_slide_form = song_slide_form
-        parse_argv(self)
 
     def execute(self) -> None:
         self.parse_file()
@@ -76,7 +86,10 @@ def main() -> None:
     colorama.init()
 
     slidegen: Slidegen = Slidegen(
-        ClassicSongTemplate, ClassicStartSlide, ClassicSongSlide
+        ClassicSongTemplate,
+        ClassicStartSlide,
+        ClassicSongSlide,
+        *parse_argv_as_tuple()
     )
     slidegen.execute()
 

+ 47 - 0
ssync.py

@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+
+"""
+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/>.
+"""
+
+import colorama
+
+from utils import clear_obs_slides_dir
+from input import validate_ssync_config, slide_selection_iterator
+from sync import sync_slide_repo, save_new_checkfile, syncing_needed
+
+
+class Ssync:
+    def __init__(self):
+        validate_ssync_config()
+
+    def execute(self):
+        if syncing_needed():
+            sync_slide_repo()
+            save_new_checkfile()
+        clear_obs_slides_dir()
+        slide_selection_iterator()
+
+
+def main() -> None:
+    colorama.init()
+
+    ssync: Ssync = Ssync()
+    ssync.execute()
+
+
+if __name__ == "__main__":
+    main()

+ 20 - 0
sync/__init__.py

@@ -0,0 +1,20 @@
+"""
+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 .sync_slide_repo import sync_slide_repo
+from .save_new_checkfile import save_new_checkfile
+from .syncing_needed import syncing_needed

+ 41 - 0
sync/save_new_checkfile.py

@@ -0,0 +1,41 @@
+"""
+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 system, path
+
+import shutil
+
+from utils import log
+
+import config as const
+
+
+def save_new_checkfile() -> None:
+    log("saving new checkfile...")
+    system(
+        'rclone md5sum {} > "{}"'.format(
+            const.RCLONE_REMOTE_DIR,
+            path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CHECKFILE_NAMING),
+        )
+    )
+    if not path.isfile(
+        path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CACHEFILE_NAMING)
+    ):
+        shutil.copyfile(
+            path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CHECKFILE_NAMING),
+            path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CACHEFILE_NAMING),
+        )

+ 31 - 0
sync/sync_slide_repo.py

@@ -0,0 +1,31 @@
+"""
+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 system
+
+from utils import log
+
+import config as const
+
+
+def sync_slide_repo() -> None:
+    log("syncing with remote slide repository...")
+    system(
+        "rclone sync -v {} {}".format(
+            const.RCLONE_REMOTE_DIR, const.RCLONE_LOCAL_DIR
+        )
+    )

+ 58 - 0
sync/syncing_needed.py

@@ -0,0 +1,58 @@
+"""
+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/>.
+"""
+
+import os
+import re
+
+from utils import log
+
+import config as const
+
+
+def syncing_needed() -> bool:
+    if not cachefiles_found():
+        return True
+
+    log("checking for remote changes...")
+    os.system(
+        "rclone md5sum {} --checkfile {} > {} 2> {}".format(
+            const.RCLONE_REMOTE_DIR,
+            os.path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CHECKFILE_NAMING),
+            os.devnull,
+            os.path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CACHEFILE_NAMING),
+        )
+    )
+
+    with open(
+        os.path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CACHEFILE_NAMING),
+        mode="r",
+        encoding="utf-8",
+    ) as cachefile_reader:
+        cachefile_content = cachefile_reader.readlines()
+    for line in cachefile_content:
+        if re.search(": ([0-9])+ differences found$", line):
+            diffs = int(line[line.rfind(":") + 1 : line.find("differences")])
+            return bool(diffs)
+    return False
+
+
+def cachefiles_found() -> bool:
+    return os.path.isfile(
+        os.path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CHECKFILE_NAMING)
+    ) and os.path.isfile(
+        os.path.join(const.SSYNC_CACHE_DIR, const.SSYNC_CACHEFILE_NAMING)
+    )

+ 2 - 0
utils/__init__.py

@@ -23,3 +23,5 @@ from .strings import (
     get_unique_structure_elements,
 )
 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

+ 36 - 0
utils/clear_obs_slides_dir.py

@@ -0,0 +1,36 @@
+"""
+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/>.
+"""
+
+import os
+import shutil
+
+from utils import log, error_msg
+
+import config as const
+
+
+def clear_obs_slides_dir() -> None:
+    log("clearing obs slides directory...")
+    for filename in os.listdir(const.OBS_SLIDES_DIR):
+        file_path = os.path.join(const.OBS_SLIDES_DIR, filename)
+        try:
+            if os.path.isfile(file_path) or os.path.islink(file_path):
+                os.unlink(file_path)
+            elif os.path.isdir(file_path):
+                shutil.rmtree(file_path)
+        except IOError as error:
+            error_msg("Failed to delete %s. Reason: %s" % (file_path, error))

+ 32 - 0
utils/create_min_obs_subdirs.py

@@ -0,0 +1,32 @@
+"""
+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/>.
+"""
+
+import os
+
+import config as const
+
+
+def create_min_obs_subdirs(count: int) -> None:
+    if count >= const.OBS_MIN_SUBDIRS:
+        return
+
+    for number in range(count, const.OBS_MIN_SUBDIRS + 1):
+        dirname = os.path.join(
+            const.OBS_SLIDES_DIR,
+            const.OBS_TARGET_SUBDIR + " " + str(number),
+        )
+        os.mkdir(dirname)