Sfoglia il codice sorgente

add readme + sanitize cli arguments

Noah Vogt 2 anni fa
parent
commit
3f628023d5
2 ha cambiato i file con 124 aggiunte e 17 eliminazioni
  1. 74 0
      README.md
  2. 50 17
      slidegen.py

+ 74 - 0
README.md

@@ -0,0 +1,74 @@
+# slidegen
+
+As the name may partially imply, **slidegen** generates song slides as images - out of a plain text input file - intended for use with [OBS]() to livestream a typical (contemporary) sunday church service.
+
+This program is also intended to be used in conjuction with [ssync](https://github.com/noahvogt/ssync), which is basically a wrapper script that automatically syncs a local copy with the remote slide repository, removes the old obs slides and lets the user interactively choose the new slides with a smart fuzzy finder.
+
+Standalone use of **slidegen** is possible and can sure fit other use cases.
+
+## Why this program exists
+
+To add song slides to OBS or similar software as input sources, there exist the following obvious options:
+
+- generating song slides via a text or presentation documents, exported in printable form and converted to images afterwards
+- generating images through image manipulation or designing software and export to images
+
+Both of these processes have *major downsides*: They are hard to automate, take a long time to create the slides, have very limited to support for bulk operations on the song repository (like wanting to change the theme and layout of all slides or changing the metadata shared by a lot of songs) and maintaing is a lot harder because of bad portability and complex source files.
+
+The only upside they have is that can be more intuitive for unexperienced 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.
+
+## Usage
+
+### Commandline Interface
+As mentionened above, this program is not made to be executed directly. Therefore the commandline interface is not yet fully stable. Generally, the syntax is as follows
+
+    ./slidegen.py SRC_PATH DEST_DIR PROMPT_INPUT
+
+with `SRC_PATH` beeing the path to the song plain text file, `DEST_DIR` the output directory where the slide image file output is placed and `PROMPT_INPUT`
+
+Here a short example:
+
+    ./slidegen.py "../songrepo/Stille Nacht.txt" "~/Documents/Song Slides 1"
+
+### Source File Layout
+
+The file is divided into two parts that are divided with at least one `\n` character and an arbitrary amount of empty lines:
+- the metadata header (top of the file)
+- the text body (bottom of the file)
+
+#### Metadata Header
+
+As the top of the file are these five metadata entries. We call them *metadata strings*:
+- title
+- book
+- text
+- melody
+- structure
+
+Their value and semantics can be pretty much whatever you want, except for an empty string.
+
+Example:
+
+
+#### Text Body
+
+### Configuration
+
+As of now, all Configuration is handled via constants in `slidegen.py`, which will change in the future. See the roadmap below.
+
+## Roadmap
+
+These are some issues that will be addressed by future development:
+
+- prevent all crashes:
+    - safe `PROMPT_INPUT` parsing
+    - handle possibly incorrect or insensible configurations
+- prevent long and many lines from cutting off text in the slide
+- integrating [ssync](https://github.com/noahvogt/ssync) into this (and hence a single) repo
+- provide ssync with the song structure, display it to the user and prevent him from entering a prompt that would crash slidegen
+- better packaging and modularisation
+- add more optional metadata strings
+
+## Licensing
+
+**slidegen** is free (as in “free speech” and also as in “free beer”) Software. It is distributed under the GNU General Public License v3 (or any later version) - see the accompanying LICENSE file for more details.

+ 50 - 17
slidegen.py

@@ -1,3 +1,5 @@
+#!/usr/bin/env python3
+
 """
 Copyright © 2022 Noah Vogt <noah@noahvogt.com>
 
@@ -17,7 +19,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from os import name, path
 from abc import ABC, abstractmethod
-from sys import argv, exit
+import sys
+import re
+
 from termcolor import colored
 import colorama
 
@@ -26,6 +30,7 @@ from wand.color import Color
 from wand.display import display
 from wand.drawing import Drawing
 from wand.font import Font
+from wand.exceptions import BlobError
 
 IMAGE_FORMAT = "jpeg"
 FILE_EXTENSION = "jpg"
@@ -90,7 +95,7 @@ METADATA_STRINGS = ("title", "book", "text", "melody", "structure")
 
 def error_msg(msg: str):
     print(colored("[*] Error: {}".format(msg), "red"))
-    exit(1)
+    sys.exit(1)
 
 
 def log(message: str):
@@ -402,11 +407,14 @@ class Slidegen:
             self.metadata["melody"],
         )
         start_slide_img.format = IMAGE_FORMAT
-        start_slide_img.save(
-            filename=path.join(
-                self.output_dir, FILE_NAMEING + "1." + FILE_EXTENSION
+        try:
+            start_slide_img.save(
+                filename=path.join(
+                    self.output_dir, FILE_NAMEING + "1." + FILE_EXTENSION
+                )
             )
-        )
+        except BlobError:
+            error_msg("could not write start slide to target directory")
         log("generating song slides...")
         for index, structure in enumerate(self.chosen_structure):
             log(
@@ -422,12 +430,15 @@ class Slidegen:
                 index,
             )
             song_slide_img.format = IMAGE_FORMAT
-            song_slide_img.save(
-                filename=path.join(
-                    self.output_dir,
-                    FILE_NAMEING + str(index + 2) + "." + FILE_EXTENSION,
+            try:
+                song_slide_img.save(
+                    filename=path.join(
+                        self.output_dir,
+                        FILE_NAMEING + str(index + 2) + "." + FILE_EXTENSION,
+                    )
                 )
-            )
+            except BlobError:
+                error_msg("could not write slide to target directory")
 
     def parse_file(self):
         self.parse_metadata()
@@ -435,14 +446,36 @@ class Slidegen:
 
     def parse_metadata(self):
         metadata_dict = dict.fromkeys(METADATA_STRINGS)
-        with open(self.song_file_path, mode="r", encoding="utf8") as opener:
-            content = strip_whitespace_list_entries(opener.readlines())
+        try:
+            with open(self.song_file_path, mode="r", encoding="utf8") as opener:
+                content = strip_whitespace_list_entries(opener.readlines())
+        except IOError:
+            error_msg(
+                "could not read the the song input file: '{}'".format(
+                    self.song_file_path
+                )
+            )
         valid_metadata_strings = list(METADATA_STRINGS)
 
         for line_nr, line in enumerate(content):
             if len(valid_metadata_strings) == 0:
                 content = content[line_nr:]
                 break
+            if not re.match("^\\S+: .+", line):
+                if line[-1] == "\n":
+                    line = line[:-1]
+                missing_metadata_strs = ""
+                for metadata_str in valid_metadata_strings:
+                    missing_metadata_strs += ", " + metadata_str
+                missing_metadata_strs = missing_metadata_strs[2:]
+                error_msg(
+                    "invalid metadata syntax on line {}:\n{}\nThe ".format(
+                        line_nr + 1, line
+                    )
+                    + "following metadata strings are still missing: {}".format(
+                        missing_metadata_strs
+                    )
+                )
             metadata_str = line[: line.index(":")]
             if metadata_str in valid_metadata_strings:
                 metadata_dict[metadata_str] = line[line.index(": ") + 2 : -1]
@@ -515,12 +548,12 @@ class Slidegen:
 
     def parse_argv(self):
         try:
-            self.song_file_path = argv[1]
-            self.output_dir = argv[2]
+            self.song_file_path = sys.argv[1]
+            self.output_dir = sys.argv[2]
         except IndexError:
-            error_msg("no arguments provided, exiting...")
+            error_msg("incorrect amount of arguments provided, exiting...")
         try:
-            self.chosen_structure = argv[3]
+            self.chosen_structure = sys.argv[3]
             if self.chosen_structure.strip() == "":
                 self.chosen_structure = ""
         except IndexError: