From 90256a99885c3391914645a0b31d144090c49dec Mon Sep 17 00:00:00 2001
From: Lars Kruse <devel@sumpfralle.de>
Date: Thu, 17 Feb 2022 00:37:42 +0100
Subject: [PATCH] style: apply "black" formatting

---
 meta.py                       |   2 +-
 src/base/config.py            |  25 ++--
 src/base/exceptions.py        |  11 +-
 src/base/logger.py            |  18 +--
 src/base/utils.py             |  15 +--
 src/client/client.py          |  74 +++++++----
 src/client/connector.py       | 132 ++++++++++++++------
 src/client/playerclient.py    |  55 +++------
 src/engine.py                 |   1 -
 src/mixer.py                  | 136 +++++++++++---------
 src/plugins/mailer.py         |  57 +++++----
 src/scheduling/api.py         | 133 ++++++++++++--------
 src/scheduling/models.py      |   1 -
 src/scheduling/programme.py   | 226 ++++++++++++++++++++++------------
 src/scheduling/scheduler.py   |  11 +-
 src/scheduling/utils.py       | 103 +++++++++-------
 tests/test_config.py          |  15 +--
 tests/test_engine_executor.py |  84 +++++++------
 tests/test_logger.py          |  10 +-
 19 files changed, 654 insertions(+), 455 deletions(-)

diff --git a/meta.py b/meta.py
index 80d8f610..30999f8a 100644
--- a/meta.py
+++ b/meta.py
@@ -7,4 +7,4 @@ __version__ = "0.9.9"
 __version_info__ = (0, 9, 9)
 __maintainer__ = "David Trattnig"
 __email__ = "david.trattnig@subsquare.at"
-__status__ = "Development"
\ No newline at end of file
+__status__ = "Development"
diff --git a/src/base/config.py b/src/base/config.py
index 00dcd70f..3336b659 100644
--- a/src/base/config.py
+++ b/src/base/config.py
@@ -32,11 +32,11 @@ class AuraConfig:
 
     Holds the Engine Configuration as in the file `engine.ini`.
     """
+
     instance = None
     ini_path = ""
     logger = None
 
-
     def __init__(self, ini_path="/etc/aura/engine.ini"):
         """
         Initializes the configuration, defaults to `/etc/aura/engine.ini`.
@@ -49,7 +49,9 @@ class AuraConfig:
         self.logger = logging.getLogger("AuraEngine")
         config_file = Path(ini_path)
         if not config_file.is_file():
-            ini_path = "%s/config/engine.ini" % Path(__file__).parent.parent.parent.absolute()
+            ini_path = (
+                "%s/config/engine.ini" % Path(__file__).parent.parent.parent.absolute()
+            )
 
         self.ini_path = ini_path
         self.load_config()
@@ -59,8 +61,6 @@ class AuraConfig:
         self.set("config_dir", os.path.dirname(ini_path))
         self.set("install_dir", os.path.realpath(__file__ + "../../../.."))
 
-
-
     @staticmethod
     def config():
         """
@@ -68,8 +68,6 @@ class AuraConfig:
         """
         return AuraConfig.instance
 
-
-
     def set(self, key, value):
         """
         Setter for some specific config property.
@@ -83,8 +81,6 @@ class AuraConfig:
         except:
             self.__dict__[key] = str(value)
 
-
-
     def get(self, key, default=None):
         """
         Getter for some specific config property.
@@ -97,7 +93,9 @@ class AuraConfig:
             if default:
                 self.set(key, default)
             else:
-                self.logger.warning("Key " + key + " not found in configfile " + self.ini_path + "!")
+                self.logger.warning(
+                    "Key " + key + " not found in configfile " + self.ini_path + "!"
+                )
                 return None
 
         value = self.__dict__[key]
@@ -105,7 +103,6 @@ class AuraConfig:
             value = os.path.expandvars(value)
         return value
 
-
     def get_database_uri(self):
         """
         Retrieves the database connection string.
@@ -127,7 +124,6 @@ class AuraConfig:
         else:
             return f"Error: invalid database type '{db_type}'"
 
-
     def load_config(self):
         """
         Set config defaults and load settings from file
@@ -137,7 +133,7 @@ class AuraConfig:
             sys.exit(1)
 
         # Read the file
-        f = open(self.ini_path, 'r')
+        f = open(self.ini_path, "r")
         ini_str = f.read()
         f.close()
 
@@ -151,10 +147,9 @@ class AuraConfig:
 
         for section in config_parser.sections():
             for key, value in config_parser.items(section):
-                v = config_parser.get(section, key).replace('"', '').strip()
+                v = config_parser.get(section, key).replace('"', "").strip()
                 self.set(key, v)
 
-
     def to_abs_path(self, path):
         """
         Transforms any given (relative) path to an absolute paths
@@ -165,14 +160,12 @@ class AuraConfig:
         else:
             return self.get("install_dir") + "/" + path
 
-
     def abs_audio_store_path(self):
         """
         Returns the absolute path to the audio store, based on the `audio_source_folder` setting.
         """
         return self.to_abs_path(self.get("audio_source_folder"))
 
-
     def abs_playlist_path(self):
         """
         Returns the absolute path to the playlist folder
diff --git a/src/base/exceptions.py b/src/base/exceptions.py
index da55ab28..fca60ad9 100644
--- a/src/base/exceptions.py
+++ b/src/base/exceptions.py
@@ -17,37 +17,42 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-
 # Scheduler Exceptions
 
+
 class NoProgrammeLoadedException(Exception):
     pass
 
+
 class NoActiveTimeslotException(Exception):
     pass
 
 
 # Soundsystem and Mixer Exceptions
 
+
 class LoadSourceException(Exception):
     pass
 
+
 class InvalidChannelException(Exception):
     pass
 
+
 class PlaylistException(Exception):
     pass
 
+
 class NoActiveEntryException(Exception):
     pass
 
 
-
 # Liquidsoap Exceptions
 
+
 class LQConnectionError(Exception):
     pass
 
+
 class LQStreamException(Exception):
     pass
-
diff --git a/src/base/logger.py b/src/base/logger.py
index d5f9e107..bfe58615 100644
--- a/src/base/logger.py
+++ b/src/base/logger.py
@@ -22,24 +22,24 @@ import logging
 from src.base.config import AuraConfig
 
 
-class AuraLogger():
+class AuraLogger:
     """
     AuraLogger Class
 
     Logger for all Aura Engine components. The default
     logger is `AuraEngine`. Other loggers are defined
-    by passing a custom name on instantiation. 
-    
+    by passing a custom name on instantiation.
+
     The logger respects the log-level as defined in the
     engine's configuration file.
     """
+
     config = None
     logger = None
 
-
     def __init__(self, config, name="AuraEngine"):
         """
-        Constructor to create a new logger defined by 
+        Constructor to create a new logger defined by
         the passed name.
 
         Args:
@@ -49,7 +49,6 @@ class AuraLogger():
         lvl = self.get_log_level()
         self.create_logger(name, lvl)
 
-
     def get_log_level(self):
         """
         Retrieves the configured log level (default=INFO).
@@ -67,7 +66,6 @@ class AuraLogger():
         else:
             return logging.CRITICAL
 
-
     def create_logger(self, name, lvl):
         """
         Creates the logger instance for the given name.
@@ -81,7 +79,9 @@ class AuraLogger():
 
         if not self.logger.hasHandlers():
             # create file handler for logger
-            file_handler = logging.FileHandler(self.config.get("log_dir") + "/"+name+".log")
+            file_handler = logging.FileHandler(
+                self.config.get("log_dir") + "/" + name + ".log"
+            )
             file_handler.setLevel(lvl)
 
             # create stream handler for logger
@@ -104,4 +104,4 @@ class AuraLogger():
 
             self.logger.debug("ADDED HANDLERS")
         else:
-            self.logger.debug("REUSED LOGGER")
\ No newline at end of file
+            self.logger.debug("REUSED LOGGER")
diff --git a/src/base/utils.py b/src/base/utils.py
index 3e9c0179..14074337 100644
--- a/src/base/utils.py
+++ b/src/base/utils.py
@@ -23,13 +23,11 @@ import time
 from enum import Enum
 
 
-
 class SimpleUtil:
     """
     A container class for simple utility methods.
     """
 
-
     @staticmethod
     def clean_dictionary(data):
         """
@@ -49,9 +47,8 @@ class SimpleUtil:
                 SimpleUtil.clean_dictionary(value)
         return data
 
-
     @staticmethod
-    def to_datetime(datetime_str:str):
+    def to_datetime(datetime_str: str):
         """
         Converts a timezone aware date-time string into `datetime`.
         """
@@ -59,7 +56,6 @@ class SimpleUtil:
             return datetime.datetime.fromisoformat(datetime_str)
         return None
 
-
     @staticmethod
     def fmt_time(timestamp):
         """
@@ -71,8 +67,7 @@ class SimpleUtil:
         Returns:
             (String): Displaying the time
         """
-        return datetime.datetime.fromtimestamp(timestamp).strftime('%H:%M:%S')
-
+        return datetime.datetime.fromtimestamp(timestamp).strftime("%H:%M:%S")
 
     @staticmethod
     def nano_to_seconds(nanoseconds):
@@ -87,7 +82,6 @@ class SimpleUtil:
         """
         return float(nanoseconds / 1000000000)
 
-
     @staticmethod
     def seconds_to_nano(seconds):
         """
@@ -101,7 +95,6 @@ class SimpleUtil:
         """
         return int(seconds * 1000000000)
 
-
     @staticmethod
     def timestamp(date_and_time=None):
         """
@@ -118,7 +111,6 @@ class SimpleUtil:
             date_and_time = datetime.datetime.now()
         return time.mktime(date_and_time.timetuple())
 
-
     @staticmethod
     def strike(text):
         """
@@ -192,7 +184,6 @@ class SimpleUtil:
         return TerminalColors.CYAN.value + text + TerminalColors.ENDC.value
 
 
-
 class TerminalColors(Enum):
     """
     Colors for formatting terminal output.
@@ -214,4 +205,4 @@ class TerminalColors(Enum):
     UNDERLINE = "\033[4m"
     STRIKE = "\u0336"
 
-    ENDC = "\033[0m"
\ No newline at end of file
+    ENDC = "\033[0m"
diff --git a/src/client/client.py b/src/client/client.py
index 1ecd030f..9d499d8f 100644
--- a/src/client/client.py
+++ b/src/client/client.py
@@ -22,12 +22,10 @@ import urllib.parse
 import configparser
 import logging
 
-from multiprocessing        import Lock
-
-from src.base.exceptions    import LQConnectionError
-from src.base.utils         import TerminalColors
-
+from multiprocessing import Lock
 
+from src.base.exceptions import LQConnectionError
+from src.base.utils import TerminalColors
 
 
 class LiquidSoapClient:
@@ -38,6 +36,7 @@ class LiquidSoapClient:
 
     #TODO Refactor class: https://gitlab.servus.at/aura/engine/-/issues/65
     """
+
     mutex = None
     logger = None
     debug = False
@@ -60,7 +59,7 @@ class LiquidSoapClient:
         self.mutex = Lock()
         self.connected = False
         self.can_connect = True
-        self.message = ''
+        self.message = ""
         self.socket = None
         self.metareader = configparser.ConfigParser()
 
@@ -74,18 +73,24 @@ class LiquidSoapClient:
             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
             self.socket.connect(self.socket_path)
         except socket.error as e:
-            msg = "Cannot connect to socketpath " + self.socket_path + ". Reason: "+str(e)
-            self.logger.critical(TerminalColors.RED.value+msg+TerminalColors.ENDC.value)
+            msg = (
+                "Cannot connect to socketpath "
+                + self.socket_path
+                + ". Reason: "
+                + str(e)
+            )
+            self.logger.critical(
+                TerminalColors.RED.value + msg + TerminalColors.ENDC.value
+            )
             self.can_connect = False
             self.connected = False
-#            raise e
+        #            raise e
         else:
             self.can_connect = True
             self.connected = True
             return True
 
-
-# AttributeError('characters_written')
+    # AttributeError('characters_written')
 
     # ------------------------------------------------------------------------------------------ #
     def is_connected(self):
@@ -113,7 +118,7 @@ class LiquidSoapClient:
         # make socket non blocking
         # self.client.setblocking(0)
 
-        data = ''
+        data = ""
 
         try:
             # set timeout
@@ -134,7 +139,9 @@ class LiquidSoapClient:
             self.mutex.release()
 
         except Exception as e:
-            self.logger.error(TerminalColors.RED.value+str(e)+TerminalColors.ENDC.value)
+            self.logger.error(
+                TerminalColors.RED.value + str(e) + TerminalColors.ENDC.value
+            )
             self.mutex.release()
 
         return data
@@ -150,7 +157,7 @@ class LiquidSoapClient:
             ret = self.read_all().splitlines()
 
             try:
-                last = ret.pop() # pop out end
+                last = ret.pop()  # pop out end
 
                 if len(ret) > 1:
                     self.message = str.join(" - ", ret)
@@ -190,23 +197,34 @@ class LiquidSoapClient:
         @return: Die Antwort des Liquidsoap-Servers
         """
 
-        param = (param.strip() if param.strip() == "" else " " + urllib.parse.unquote(param.strip()))
+        param = (
+            param.strip()
+            if param.strip() == ""
+            else " " + urllib.parse.unquote(param.strip())
+        )
         if self.connected:
             # print namespace + '.' + command + param + "\n"
             if namespace == "":
                 message = str(command) + str(param) + str("\n")
             else:
-                message = str(namespace) + str(".") + str(command) + str(param) + str("\n")
+                message = (
+                    str(namespace) + str(".") + str(command) + str(param) + str("\n")
+                )
 
             try:
                 if not self.disable_logging:
-                    self.logger.debug("LiquidSoapClient sending to LiquidSoap Server: " + message[0:len(message)-1])
+                    self.logger.debug(
+                        "LiquidSoapClient sending to LiquidSoap Server: "
+                        + message[0 : len(message) - 1]
+                    )
 
                 # send all the stuff over the socket to liquidsoap server
                 self.socket.sendall(message.encode())
 
                 if not self.disable_logging:
-                    self.logger.debug("LiquidSoapClient waiting for reply from LiquidSoap Server")
+                    self.logger.debug(
+                        "LiquidSoapClient waiting for reply from LiquidSoap Server"
+                    )
 
                 # wait for reply
                 self.read()
@@ -214,7 +232,15 @@ class LiquidSoapClient:
                 if not self.disable_logging:
                     self.logger.debug("LiquidSoapClient got reply: " + self.message)
             except BrokenPipeError as e:
-                self.logger.error(TerminalColors.RED.value+"Detected a problem with liquidsoap connection while sending: " + message + ". Reason: " + str(e) + "! Trying to reconnect."+TerminalColors.RED.value)
+                self.logger.error(
+                    TerminalColors.RED.value
+                    + "Detected a problem with liquidsoap connection while sending: "
+                    + message
+                    + ". Reason: "
+                    + str(e)
+                    + "! Trying to reconnect."
+                    + TerminalColors.RED.value
+                )
                 self.connect()
                 raise
 
@@ -236,7 +262,7 @@ class LiquidSoapClient:
         @return: the response of the liquidsoap server
         """
         if self.connected:
-            self.command('help', '')
+            self.command("help", "")
             return self.message
 
     # ------------------------------------------------------------------------------------------ #
@@ -247,8 +273,8 @@ class LiquidSoapClient:
         @return: the response of the liquidsoap server
         """
         if self.connected:
-            message = 'version'
-            self.command(message, '')
+            message = "version"
+            self.command(message, "")
             return self.message
 
     # ------------------------------------------------------------------------------------------ #
@@ -260,7 +286,7 @@ class LiquidSoapClient:
         """
 
         if self.connected:
-            self.command('uptime', '')
+            self.command("uptime", "")
             return self.message
 
     # ------------------------------------------------------------------------------------------ #
@@ -273,4 +299,4 @@ class LiquidSoapClient:
 
         if self.connected:
             self.command("", "quit")
-            return self.message
\ No newline at end of file
+            return self.message
diff --git a/src/client/connector.py b/src/client/connector.py
index b8387ff7..9345538e 100644
--- a/src/client/connector.py
+++ b/src/client/connector.py
@@ -20,19 +20,19 @@
 import logging
 import time
 
-from src.base.config                import AuraConfig
-from src.base.utils                 import TerminalColors, SimpleUtil as SU
-from src.base.exceptions            import LQConnectionError
-from src.client.playerclient        import LiquidSoapPlayerClient
+from src.base.config import AuraConfig
+from src.base.utils import TerminalColors, SimpleUtil as SU
+from src.base.exceptions import LQConnectionError
+from src.client.playerclient import LiquidSoapPlayerClient
 
 
-
-class PlayerConnector():
+class PlayerConnector:
     """
     Establishes a Socket connection to Liquidsoap.
 
     #TODO Refactor class: https://gitlab.servus.at/aura/engine/-/issues/65
     """
+
     client = None
     logger = None
     transaction = 0
@@ -41,7 +41,6 @@ class PlayerConnector():
     event_dispatcher = None
     has_connection = None
 
-
     def __init__(self, event_dispatcher):
         """
         Constructor
@@ -55,7 +54,6 @@ class PlayerConnector():
         self.event_dispatcher = event_dispatcher
         self.has_connection = False
 
-
     def send_lqc_command(self, namespace, command, *args):
         """
         Ein Kommando an Liquidsoap senden
@@ -74,36 +72,45 @@ class PlayerConnector():
         try:
             if not self.disable_logging:
                 if command == "":
-                    self.logger.debug("LiquidSoapCommunicator is calling " + str(namespace) + str(args))
+                    self.logger.debug(
+                        "LiquidSoapCommunicator is calling "
+                        + str(namespace)
+                        + str(args)
+                    )
                 else:
-                    self.logger.debug("LiquidSoapCommunicator is calling " + str(namespace) + "." + str(command) + str(args))
+                    self.logger.debug(
+                        "LiquidSoapCommunicator is calling "
+                        + str(namespace)
+                        + "."
+                        + str(command)
+                        + str(args)
+                    )
 
             # call wanted function ...
 
             # FIXME REFACTOR all calls in a common way
-            if command in  [
-                    "queue_push",
-                    "queue_seek",
-                    "queue_clear",
-                    "playlist_uri_set",
-                    "playlist_uri_clear",
-                    "stream_set_url",
-                    "stream_start",
-                    "stream_stop",
-                    "stream_status",
-                    ]:
+            if command in [
+                "queue_push",
+                "queue_seek",
+                "queue_clear",
+                "playlist_uri_set",
+                "playlist_uri_clear",
+                "stream_set_url",
+                "stream_start",
+                "stream_stop",
+                "stream_status",
+            ]:
 
                 func = getattr(lqs_instance, command)
                 result = func(str(namespace), *args)
 
-            elif namespace == "mixer": # or namespace == "mixer_fallback":
+            elif namespace == "mixer":  # or namespace == "mixer_fallback":
                 func = getattr(lqs_instance, command)
                 result = func(str(namespace), *args)
             else:
                 func = getattr(lqs_instance, namespace)
                 result = func(command, *args)
 
-
             if not self.disable_logging:
                 self.logger.debug("LiquidSoapCommunicator got response " + str(result))
 
@@ -112,14 +119,26 @@ class PlayerConnector():
             return result
 
         except LQConnectionError as e:
-            self.logger.error("Connection Error when sending " + str(namespace) + "." + str(command) + str(args))
+            self.logger.error(
+                "Connection Error when sending "
+                + str(namespace)
+                + "."
+                + str(command)
+                + str(args)
+            )
             if self.try_to_reconnect():
                 time.sleep(0.2)
                 self.connection_attempts += 1
                 if self.connection_attempts < 5:
                     # reconnect
                     self.__open_conn(self.client)
-                    self.logger.info("Trying to resend " + str(namespace) + "." + str(command) + str(args))
+                    self.logger.info(
+                        "Trying to resend "
+                        + str(namespace)
+                        + "."
+                        + str(command)
+                        + str(args)
+                    )
                     # grab return value
                     retval = self.send_lqc_command(namespace, command, *args)
                     # disconnect
@@ -128,19 +147,31 @@ class PlayerConnector():
                     return retval
                 else:
                     if command == "":
-                        msg = "Rethrowing Exception while trying to send " + str(namespace) + str(args)
+                        msg = (
+                            "Rethrowing Exception while trying to send "
+                            + str(namespace)
+                            + str(args)
+                        )
                     else:
-                        msg = "Rethrowing Exception while trying to send " + str(namespace) + "." + str(command) + str(args)
+                        msg = (
+                            "Rethrowing Exception while trying to send "
+                            + str(namespace)
+                            + "."
+                            + str(command)
+                            + str(args)
+                        )
 
                     self.logger.info(msg)
                     self.disable_transaction(socket=self.client, force=True)
                     raise e
             else:
-                self.event_dispatcher.on_critical("Critical Liquidsoap connection issue", \
-                    "Could not connect to Liquidsoap after multiple attempts", e)
+                self.event_dispatcher.on_critical(
+                    "Critical Liquidsoap connection issue",
+                    "Could not connect to Liquidsoap after multiple attempts",
+                    e,
+                )
                 raise e
 
-
     # ------------------------------------------------------------------------------------------ #
     def try_to_reconnect(self):
         self.enable_transaction()
@@ -154,7 +185,12 @@ class PlayerConnector():
 
         self.transaction = self.transaction + 1
 
-        self.logger.debug(TerminalColors.WARNING.value + "Enabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)
+        self.logger.debug(
+            TerminalColors.WARNING.value
+            + "Enabling transaction! cnt: "
+            + str(self.transaction)
+            + TerminalColors.ENDC.value
+        )
 
         if self.transaction > 1:
             return
@@ -164,11 +200,14 @@ class PlayerConnector():
         except FileNotFoundError:
             self.disable_transaction(socket=socket, force=True)
             subject = "CRITICAL Exception when connecting to Liquidsoap"
-            msg = "socket file " + socket.socket_path + " not found. Is liquidsoap running?"
+            msg = (
+                "socket file "
+                + socket.socket_path
+                + " not found. Is liquidsoap running?"
+            )
             self.logger.critical(SU.red(msg))
             # Not using this for now, as it should be triggered by "on_sick(..)" as well
-            #self.event_dispatcher.on_critical(subject, msg, None)
-
+            # self.event_dispatcher.on_critical(subject, msg, None)
 
     # ------------------------------------------------------------------------------------------ #
     def disable_transaction(self, socket=None, force=False):
@@ -181,13 +220,22 @@ class PlayerConnector():
             self.transaction = self.transaction - 1
 
             # debug msg
-            self.logger.debug(TerminalColors.WARNING.value + "DISabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)
+            self.logger.debug(
+                TerminalColors.WARNING.value
+                + "DISabling transaction! cnt: "
+                + str(self.transaction)
+                + TerminalColors.ENDC.value
+            )
 
             # return if connection is still needed
             if self.transaction > 0:
                 return
         else:
-            self.logger.debug(TerminalColors.WARNING.value + "Forcefully DISabling transaction! " + TerminalColors.ENDC.value)
+            self.logger.debug(
+                TerminalColors.WARNING.value
+                + "Forcefully DISabling transaction! "
+                + TerminalColors.ENDC.value
+            )
 
         # close conn and set transactioncounter to 0
         self.__close_conn(socket)
@@ -202,7 +250,11 @@ class PlayerConnector():
         if self.has_connection:
             return
 
-        self.logger.debug(TerminalColors.GREEN.value + "LiquidSoapCommunicator opening conn" + TerminalColors.ENDC.value)
+        self.logger.debug(
+            TerminalColors.GREEN.value
+            + "LiquidSoapCommunicator opening conn"
+            + TerminalColors.ENDC.value
+        )
 
         # try to connect
         socket.connect()
@@ -222,4 +274,8 @@ class PlayerConnector():
         # socket.byebye()
 
         # debug msg
-        self.logger.debug(TerminalColors.BLUE.value + "LiquidSoapCommunicator closed conn" + TerminalColors.ENDC.value)
+        self.logger.debug(
+            TerminalColors.BLUE.value
+            + "LiquidSoapCommunicator closed conn"
+            + TerminalColors.ENDC.value
+        )
diff --git a/src/client/playerclient.py b/src/client/playerclient.py
index 7d96f43c..3aa2fb87 100644
--- a/src/client/playerclient.py
+++ b/src/client/playerclient.py
@@ -22,7 +22,7 @@ from src.client.client import LiquidSoapClient
 
 class LiquidSoapPlayerClient(LiquidSoapClient):
 
-    #TODO Refactor class: https://gitlab.servus.at/aura/engine/-/issues/65
+    # TODO Refactor class: https://gitlab.servus.at/aura/engine/-/issues/65
 
     #
     # Mixer
@@ -48,14 +48,13 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
 
     #     return "LiquidSoapPlayerClient does not understand mixer."+command+str(args)
 
-
     # ------------------------------------------------------------------------------------------ #
     def mixer_inputs(self, mixer_id):
         # send command
         self.command(mixer_id, "inputs")
 
         # convert to list and return it
-        return self.message.strip().split(' ')
+        return self.message.strip().split(" ")
 
     # ------------------------------------------------------------------------------------------ #
     def mixer_status(self, mixer_id, pos=""):
@@ -69,7 +68,6 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         self.command(mixer_id, "status", str(pos))
         return self.message
 
-
     def mixer_volume(self, mixer_id, pos, volume):
         """
         Sets some mixer channel to the given volume
@@ -84,7 +82,6 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         self.command(mixer_id, "volume", str(pos) + " " + str(volume))
         return self.message
 
-
     def mixer_select(self, mixer_id, pos, select):
         """
         Selects some mixer channel or vice versa.
@@ -99,7 +96,6 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         self.command(mixer_id, "select", str(pos) + " " + str(select).lower())
         return self.message
 
-
     def mixer_activate(self, mixer_id, pos, activate):
         """
         Selects some mixer channel and increases the volume to 100 or vice versa.
@@ -114,7 +110,6 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         self.command(mixer_id, "activate", str(pos) + " " + str(activate).lower())
         return self.message
 
-
     #
     # Queues
     #
@@ -127,10 +122,9 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
             channel (String):   Liquidsoap Source ID
             uri (String):       Path to the file
         """
-        self.command(channel, 'push', uri)
+        self.command(channel, "push", uri)
         return self.message
 
-
     def queue_seek(self, channel, duration):
         """
         Forward the playing `equeue` track/playlist of the given channel.
@@ -142,10 +136,9 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         Returns:
             Liquidsoap server response
         """
-        self.command(channel, 'seek', str(duration))
+        self.command(channel, "seek", str(duration))
         return self.message
 
-
     def queue_clear(self, channel):
         """
         Clears all `equeue` playlist entries of the given channel.
@@ -157,10 +150,9 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         Returns:
             Liquidsoap server response
         """
-        self.command(channel, 'clear')
+        self.command(channel, "clear")
         return self.message
 
-
     #
     # Playlist
     #
@@ -176,10 +168,9 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         Returns:
             Liquidsoap server response
         """
-        self.command(channel, 'uri', uri)
+        self.command(channel, "uri", uri)
         return self.message
 
-
     def playlist_uri_clear(self, channel):
         """
         Clears the URI of a playlist source.
@@ -191,10 +182,9 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         Returns:
             Liquidsoap server response
         """
-        self.command(channel, 'clear')
+        self.command(channel, "clear")
         return self.message
 
-
     #
     # Stream
     #
@@ -203,34 +193,30 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         """
         Sets the URL on the given HTTP channel.
         """
-        self.command(channel, 'url', url)
+        self.command(channel, "url", url)
         return self.message
 
-
     def stream_start(self, channel):
         """
         Starts the HTTP stream set with `stream_set_url` on the given channel.
         """
-        self.command(channel, 'start')
+        self.command(channel, "start")
         return self.message
 
-
     def stream_stop(self, channel):
         """
         Stops the HTTP stream on the given channel.
         """
-        self.command(channel, 'stop')
+        self.command(channel, "stop")
         return self.message
 
-
     def stream_status(self, channel):
         """
         Returns the status of the HTTP stream on the given channel.
         """
-        self.command(channel, 'status')
+        self.command(channel, "status")
         return self.message
 
-
     #
     # General Entries
     #
@@ -248,25 +234,22 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         self.command("request", "status", str(rid))
         return self.message
 
-
     #
     # Other
     #
 
-    def uptime(self, command=""): # no command will come
+    def uptime(self, command=""):  # no command will come
         """
         Retrieves how long the engine is running already.
         """
         return self.command("", "uptime")
 
-
-    def version(self, command=""): # no command will come
+    def version(self, command=""):  # no command will come
         """
         Retrieves the Liquidsoap version.
         """
         return self.command("", "version")
 
-
     def engine(self, command, *args):
         """
         Retrieves the state of all input and outputs.
@@ -274,17 +257,17 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         if command == "state":
             return self.engine_state()
 
-        return "LiquidSoapPlayerClient does not understand engine." + command + str(args)
-
+        return (
+            "LiquidSoapPlayerClient does not understand engine." + command + str(args)
+        )
 
     def engine_state(self):
         """
         Retrieves the state of all input and outputs.
         """
-        self.command('auraengine', 'state')
+        self.command("auraengine", "state")
         return self.message
 
-
     # ------------------------------------------------------------------------------------------ #
     # def skip(self, namespace="playlist", pos=""):
     #     """
@@ -446,7 +429,7 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
         @rtype:  string
         @return: Die Antwort des Liquidsoap-Servers
         """
-        self.command('volume', namespace, str(pos) + ' ' + str(volume))
+        self.command("volume", namespace, str(pos) + " " + str(volume))
         return self.message
 
     # ------------------------------------------------------------------------------------------ #
@@ -475,4 +458,4 @@ class LiquidSoapPlayerClient(LiquidSoapClient):
     #     else:
     #         self.success('00', channels)
 
-    #     self.notifyClient()
\ No newline at end of file
+    #     self.notifyClient()
diff --git a/src/engine.py b/src/engine.py
index 392b8982..2f62578a 100644
--- a/src/engine.py
+++ b/src/engine.py
@@ -583,7 +583,6 @@ class Player:
             self.mixer.channel_activate(channel.value, True)
             self.mixer.channel_activate(channel.value, False)
 
-
         Thread(target=clean_up).start()
 
     #
diff --git a/src/mixer.py b/src/mixer.py
index a1ed2a67..9a3efa9c 100644
--- a/src/mixer.py
+++ b/src/mixer.py
@@ -1,4 +1,3 @@
-
 #
 # Aura Engine (https://gitlab.servus.at/aura/engine)
 #
@@ -18,27 +17,24 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-
 import logging
 import time
 
-from enum                   import Enum
-
-from src.base.exceptions    import LQConnectionError
-from src.base.utils         import SimpleUtil as SU
+from enum import Enum
 
+from src.base.exceptions import LQConnectionError
+from src.base.utils import SimpleUtil as SU
 
 
 class MixerType(Enum):
     """
     Types of mixers mapped to the Liquidsoap mixer ids.
     """
+
     MAIN = "mixer"
     # FALLBACK = "mixer_fallback"
 
 
-
-
 class MixerUtil:
     """
     Little helpers for the mixer.
@@ -57,11 +53,11 @@ class MixerUtil:
         return s
 
 
-
-class Mixer():
+class Mixer:
     """
     A virtual mixer.
     """
+
     config = None
     logger = None
     connector = None
@@ -70,7 +66,6 @@ class Mixer():
     fade_in_active = None
     fade_out_active = None
 
-
     def __init__(self, config, mixer_id, connector):
         """
         Constructor
@@ -86,25 +81,22 @@ class Mixer():
         self.connector = connector
         self.mixer_initialize()
 
-
     #
     #   Mixer
     #
 
-
     def mixer_initialize(self):
         """
-            - Pull all faders down to volume 0.
-            - Initialize default channels per type
+        - Pull all faders down to volume 0.
+        - Initialize default channels per type
         """
         self.connector.enable_transaction()
-        time.sleep(1) # TODO Check is this is still required
+        time.sleep(1)  # TODO Check is this is still required
         channels = self.mixer_channels_reload()
         for channel in channels:
             self.channel_volume(channel, "0")
         self.connector.disable_transaction()
 
-
     def mixer_status(self):
         """
         Returns the state of all mixer channels
@@ -122,16 +114,16 @@ class Mixer():
         self.connector.disable_transaction()
         return inputstate
 
-
     def mixer_channels(self):
         """
         Retrieves all mixer channels
         """
         if self.channels is None or len(self.channels) == 0:
-            self.channels = self.connector.send_lqc_command(self.mixer_id.value, "mixer_inputs")
+            self.channels = self.connector.send_lqc_command(
+                self.mixer_id.value, "mixer_inputs"
+            )
         return self.channels
 
-
     def mixer_channels_selected(self):
         """
         Retrieves all selected channels of the mixer.
@@ -151,7 +143,6 @@ class Mixer():
         self.connector.disable_transaction()
         return activeinputs
 
-
     def mixer_channels_except(self, input_type):
         """
         Retrieves all mixer channels except the ones of the given type.
@@ -160,13 +151,15 @@ class Mixer():
             activemixer_copy = self.mixer_channels().copy()
             activemixer_copy.remove(input_type)
         except ValueError as e:
-            self.logger.error("Requested channel (%s) not in channel-list. Reason: %s" % (input_type, str(e)))
+            self.logger.error(
+                "Requested channel (%s) not in channel-list. Reason: %s"
+                % (input_type, str(e))
+            )
         except AttributeError:
             self.logger.critical("Empty channel list")
 
         return activemixer_copy
 
-
     def mixer_channels_reload(self):
         """
         Reloads all mixer channels.
@@ -174,7 +167,6 @@ class Mixer():
         self.channels = None
         return self.mixer_channels()
 
-
     #
     #   Channel
     #
@@ -192,11 +184,12 @@ class Mixer():
         channels = self.mixer_channels()
         index = channels.index(channel)
         if index < 0:
-            self.logger.critical(f"There's no valid channel number for channel ID '{channel.value}'")
+            self.logger.critical(
+                f"There's no valid channel number for channel ID '{channel.value}'"
+            )
             return None
         return index
 
-
     def channel_status(self, channel_number):
         """
         Retrieves the status of a channel identified by the channel number.
@@ -207,8 +200,9 @@ class Mixer():
         Returns:
             (String):   Channel status info as a String
         """
-        return self.connector.send_lqc_command(self.mixer_id.value, "mixer_status", channel_number)
-
+        return self.connector.send_lqc_command(
+            self.mixer_id.value, "mixer_status", channel_number
+        )
 
     def channel_select(self, channel, select):
         """
@@ -228,12 +222,14 @@ class Mixer():
             if len(channel) < 1:
                 self.logger.critical("Cannot select channel. There are no channels!")
             else:
-                message = self.connector.send_lqc_command(self.mixer_id.value, "mixer_select", index, select)
+                message = self.connector.send_lqc_command(
+                    self.mixer_id.value, "mixer_select", index, select
+                )
                 return message
         except Exception as e:
-            self.logger.critical("Ran into exception when selecting channel. Reason: " + str(e))
-
-
+            self.logger.critical(
+                "Ran into exception when selecting channel. Reason: " + str(e)
+            )
 
     def channel_activate(self, channel, activate):
         """
@@ -255,11 +251,14 @@ class Mixer():
             if len(channel) < 1:
                 self.logger.critical("Cannot activate channel. There are no channels!")
             else:
-                message = self.connector.send_lqc_command(self.mixer_id.value, "mixer_activate", index, activate)
+                message = self.connector.send_lqc_command(
+                    self.mixer_id.value, "mixer_activate", index, activate
+                )
                 return message
         except Exception as e:
-            self.logger.critical("Ran into exception when activating channel. Reason: " + str(e))
-
+            self.logger.critical(
+                "Ran into exception when activating channel. Reason: " + str(e)
+            )
 
     def channel_current_volume(self, channel):
         """
@@ -272,10 +271,11 @@ class Mixer():
         if volume:
             return int(volume.split("%")[0])
         else:
-            self.logger.error(f"Invalid volume for channel {channel.value} (status: '{status}'")
+            self.logger.error(
+                f"Invalid volume for channel {channel.value} (status: '{status}'"
+            )
             return 0
 
-
     def channel_volume(self, channel, volume):
         """
         Set volume of a channel
@@ -299,31 +299,51 @@ class Mixer():
 
         try:
             if len(channel) < 1:
-                msg = SU.red("Cannot set volume of channel " + channel + " to " + str(volume) + "! There are no channels.")
+                msg = SU.red(
+                    "Cannot set volume of channel "
+                    + channel
+                    + " to "
+                    + str(volume)
+                    + "! There are no channels."
+                )
                 self.logger.warning(msg)
             else:
-                message = self.connector.send_lqc_command(self.mixer_id.value, "mixer_volume", str(index), str(int(volume)))
+                message = self.connector.send_lqc_command(
+                    self.mixer_id.value, "mixer_volume", str(index), str(int(volume))
+                )
 
                 if not self.connector.disable_logging:
-                    if message.find('volume=' + str(volume) + '%'):
-                        self.logger.info(SU.pink("Set volume of channel '%s' to %s" % (channel, str(volume))))
+                    if message.find("volume=" + str(volume) + "%"):
+                        self.logger.info(
+                            SU.pink(
+                                "Set volume of channel '%s' to %s"
+                                % (channel, str(volume))
+                            )
+                        )
                     else:
-                        msg = SU.red("Setting volume of channel " + channel + " has gone wrong! Liquidsoap message: " + message)
+                        msg = SU.red(
+                            "Setting volume of channel "
+                            + channel
+                            + " has gone wrong! Liquidsoap message: "
+                            + message
+                        )
                         self.logger.warning(msg)
 
                 return message
-        except AttributeError as e: #(LQConnectionError, AttributeError):
+        except AttributeError as e:  # (LQConnectionError, AttributeError):
             self.connector.disable_transaction(force=True)
-            msg = SU.red("Ran into exception when setting volume of channel " + channel + ". Reason: " + str(e))
+            msg = SU.red(
+                "Ran into exception when setting volume of channel "
+                + channel
+                + ". Reason: "
+                + str(e)
+            )
             self.logger.error(msg)
 
-
-
     #
     #   Fading
     #
 
-
     def fade_in(self, channel, volume):
         """
         Performs a fade-in for the given channel.
@@ -339,10 +359,14 @@ class Mixer():
             current_volume = self.channel_current_volume(channel)
 
             if current_volume == volume:
-                self.logger.warning(f"Current volume for channel {channel.value} is already at target volume of {volume}% SKIPPING...")
+                self.logger.warning(
+                    f"Current volume for channel {channel.value} is already at target volume of {volume}% SKIPPING..."
+                )
                 return
             elif current_volume > volume:
-                self.logger.warning(f"Current volume {current_volume}% of channel {channel.value} exceeds target volume of {volume}% SKIPPING...")
+                self.logger.warning(
+                    f"Current volume {current_volume}% of channel {channel.value} exceeds target volume of {volume}% SKIPPING..."
+                )
                 return
 
             fade_in_time = float(self.config.get("fade_in_time"))
@@ -353,8 +377,10 @@ class Mixer():
 
                 step = fade_in_time / target_volume
 
-                msg = "Starting to fading-in '%s'. Step is %ss and target volume is %s." % \
-                    (channel, str(step), str(target_volume))
+                msg = (
+                    "Starting to fading-in '%s'. Step is %ss and target volume is %s."
+                    % (channel, str(step), str(target_volume))
+                )
                 self.logger.info(SU.pink(msg))
 
                 # Enable logging, which might have been disabled in a previous fade-out
@@ -378,8 +404,6 @@ class Mixer():
             return False
         return True
 
-
-
     def fade_out(self, channel, volume=None):
         """
         Performs a fade-out for the given channel starting at its current volume.
@@ -397,7 +421,9 @@ class Mixer():
                 volume = current_volume
 
             if current_volume == 0:
-                self.logger.warning(f"Current volume for channel {channel.value} is already at target volume of 0%. SKIPPING...")
+                self.logger.warning(
+                    f"Current volume for channel {channel.value} is already at target volume of 0%. SKIPPING..."
+                )
                 return
 
             fade_out_time = float(self.config.get("fade_out_time"))
@@ -413,7 +439,7 @@ class Mixer():
                 self.connector.client.disable_logging = True
 
                 for i in range(volume):
-                    self.channel_volume(channel.value, volume-i-1)
+                    self.channel_volume(channel.value, volume - i - 1)
                     time.sleep(step)
 
                 msg = "Finished with fading-out '%s'" % channel
diff --git a/src/plugins/mailer.py b/src/plugins/mailer.py
index b08825b6..c407467a 100644
--- a/src/plugins/mailer.py
+++ b/src/plugins/mailer.py
@@ -26,19 +26,17 @@ from src.base.config import AuraConfig
 from src.base.utils import SimpleUtil as SU
 
 
-
 class MailingException(Exception):
     """
     Thrown when some mail cannot be sent.
     """
 
 
-
-
-class AuraMailer():
+class AuraMailer:
     """
     Event handler to send emails to Aura administrators and programme coordinators.
     """
+
     logger = None
     engine = None
     mail = None
@@ -55,12 +53,10 @@ class AuraMailer():
         self.engine = engine
         self.mail = MailService()
 
-
     #
     #   METHODS
     #
 
-
     def on_fallback_active(self, timeslot, fallback_type):
         """
         Called when a fallback is activated for the given timeslot,
@@ -69,11 +65,15 @@ class AuraMailer():
         show = "EMPTY TIMESLOT"
         show_id = ""
         timeframe = ""
-        
+
         if timeslot:
             show = timeslot.show_name
             show_id = "The ID of the show is: " + str(timeslot.show_id)
-            timeframe = SU.fmt_time(timeslot.start_unix) + " - " + SU.fmt_time(timeslot.end_unix)
+            timeframe = (
+                SU.fmt_time(timeslot.start_unix)
+                + " - "
+                + SU.fmt_time(timeslot.end_unix)
+            )
 
         subject = f"Fallback for show '{show}' activated"
         message = "Dear programme coordinator, \n\n"
@@ -82,42 +82,42 @@ class AuraMailer():
         self.logger.debug(message)
         self.mail.notify_coordinator(subject, message)
 
-
-
     def on_sick(self, data):
         """
         Called when the engine is in some unhealthy state.
         """
         subject = "ERROR - Engine turned into some INVALID STATE!"
-        message = "There's an issue with your AURA Engine '%s':\n\n%s" % (data.get("engine_id"), data.get("status"))
+        message = "There's an issue with your AURA Engine '%s':\n\n%s" % (
+            data.get("engine_id"),
+            data.get("status"),
+        )
         self.mail.notify_admin(subject, message)
-            
-
 
     def on_resurrect(self, data):
         """
         Called when the engine turned healthy again after being sick.
         """
         subject = "OK - Engine became healthy again"
-        message = "Good news, things seem fine again with your AURA Engine '%s':\n\n%s" % (data.get("engine_id"), data.get("status"))
+        message = (
+            "Good news, things seem fine again with your AURA Engine '%s':\n\n%s"
+            % (data.get("engine_id"), data.get("status"))
+        )
         self.mail.notify_admin(subject, message)
 
-
-
     def on_critical(self, subject, message, data=None):
         """
         Callend when some critical event occurs
         """
-        if not data: data = ""
+        if not data:
+            data = ""
         self.mail.notify_admin(subject, message + "\n\n" + str(data))
 
 
-
-
-class MailService():
+class MailService:
     """
     Service to send emails to Aura administrators.
     """
+
     config = None
     logger = None
     admin_mails = None
@@ -140,7 +140,6 @@ class MailService():
     #   METHODS
     #
 
-
     def notify_admin(self, subject, body):
         """
         Sends an email to the administrator(s) as defined in the configuration.
@@ -150,7 +149,11 @@ class MailService():
             body (String):      The email body text
         """
         if self.admin_mails_enabled == "false":
-            self.logger.warning(SU.red("No admin mail sent, because doing so is disabled in engine.ini!"))
+            self.logger.warning(
+                SU.red(
+                    "No admin mail sent, because doing so is disabled in engine.ini!"
+                )
+            )
             return False
 
         admin_mails = self.admin_mails.split()
@@ -158,8 +161,6 @@ class MailService():
         for mail_to in admin_mails:
             self.send(mail_to, subject, body)
 
-
-
     def notify_coordinator(self, subject, body):
         """
         Sends an email to the programme coordinator(s) as defined in the configuration.
@@ -169,7 +170,11 @@ class MailService():
             body (String):      The email body text
         """
         if self.coordinator_mails_enabled == "false":
-            self.logger.warning(SU.yellow("No programme coordinator mail sent, because doing so is disabled in engine.ini!"))
+            self.logger.warning(
+                SU.yellow(
+                    "No programme coordinator mail sent, because doing so is disabled in engine.ini!"
+                )
+            )
             return False
 
         coordinator_mails = self.coordinator_mails.split()
@@ -177,8 +182,6 @@ class MailService():
         for mail_to in coordinator_mails:
             self.send(mail_to, subject, body)
 
-
-
     def send(self, mail_to, subject, body):
         """
         Sends an email to the given address.
diff --git a/src/scheduling/api.py b/src/scheduling/api.py
index bbed1038..c2efed4d 100644
--- a/src/scheduling/api.py
+++ b/src/scheduling/api.py
@@ -1,4 +1,3 @@
-
 #
 # Aura Engine (https://gitlab.servus.at/aura/engine)
 #
@@ -18,7 +17,6 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-
 import logging
 import requests
 import queue
@@ -29,12 +27,12 @@ from src.base.utils import SimpleUtil as SU
 from src.scheduling.utils import TimeslotFilter
 
 
-
 class ApiFetcher(threading.Thread):
     """
     Fetches the timeslots, playlists and playlist entries as JSON
     via the API endpoints of Steering and Tank.
     """
+
     config = None
     logging = None
     queue = None
@@ -48,8 +46,6 @@ class ApiFetcher(threading.Thread):
     tank_session = None
     tank_secret = None
 
-
-
     def __init__(self, config):
         """
         Constructor
@@ -64,8 +60,6 @@ class ApiFetcher(threading.Thread):
         self.stop_event = threading.Event()
         threading.Thread.__init__(self)
 
-
-
     def run(self):
         """
         Fetch timeslot data from the API.
@@ -75,7 +69,9 @@ class ApiFetcher(threading.Thread):
         """
         try:
             fetched_timeslots = self.fetch()
-            self.logger.debug("Timeslot data fetched from API: " + str(fetched_timeslots))
+            self.logger.debug(
+                "Timeslot data fetched from API: " + str(fetched_timeslots)
+            )
 
             # If nothing is fetched, return
             if not fetched_timeslots:
@@ -92,21 +88,16 @@ class ApiFetcher(threading.Thread):
         # Terminate the thread
         return
 
-
-
     #
     #   METHODS
     #
 
-
     def get_fetched_data(self):
         """
         Retrieves the fetched data from the queue.
         """
         return self.queue.get()
 
-
-
     def fetch(self):
         """
         Retrieve all required data from the API.
@@ -122,10 +113,14 @@ class ApiFetcher(threading.Thread):
         for timeslot in self.fetched_timeslot_data:
 
             if "schedule_default_playlist_id" in timeslot:
-                timeslot["default_schedule_playlist_id"] = timeslot["schedule_default_playlist_id"]
+                timeslot["default_schedule_playlist_id"] = timeslot[
+                    "schedule_default_playlist_id"
+                ]
                 timeslot["schedule_fallback_id"] = None
             if "show_default_playlist_id" in timeslot:
-                timeslot["default_show_playlist_id"] = timeslot["show_default_playlist_id"]
+                timeslot["default_show_playlist_id"] = timeslot[
+                    "show_default_playlist_id"
+                ]
                 timeslot["show_fallback_id"] = None
 
         self.logger.debug("Fetching playlists from TANK")
@@ -136,10 +131,15 @@ class ApiFetcher(threading.Thread):
 
                 # Skip timeslot if no start or end is given
                 if "start" not in timeslot:
-                    self.logger.warning("No start of timeslot given. Skipping timeslot: " + str(timeslot))
+                    self.logger.warning(
+                        "No start of timeslot given. Skipping timeslot: "
+                        + str(timeslot)
+                    )
                     timeslot = None
                 if "end" not in timeslot:
-                    self.logger.warning("No end of timeslot given. Skipping timeslot: " + str(timeslot))
+                    self.logger.warning(
+                        "No end of timeslot given. Skipping timeslot: " + str(timeslot)
+                    )
                     timeslot = None
 
                 if timeslot:
@@ -151,8 +151,6 @@ class ApiFetcher(threading.Thread):
 
         return return_data
 
-
-
     def fetch_timeslot_data(self):
         """
         Fetches timeslot data from Steering.
@@ -161,18 +159,26 @@ class ApiFetcher(threading.Thread):
             ([Timeslot]):   An array of timeslots
         """
         timeslots = None
-        headers = { "content-type": "application/json" }
+        headers = {"content-type": "application/json"}
 
         try:
             self.logger.debug("Fetch timeslots from Steering API...")
-            response = requests.get(self.steering_calendar_url, data=None, headers=headers)
+            response = requests.get(
+                self.steering_calendar_url, data=None, headers=headers
+            )
             if not response.status_code == 200:
-                self.logger.critical(SU.red("HTTP Status: %s | Timeslots could not be fetched! Response: %s" % \
-                    (str(response.status_code), response.text)))
+                self.logger.critical(
+                    SU.red(
+                        "HTTP Status: %s | Timeslots could not be fetched! Response: %s"
+                        % (str(response.status_code), response.text)
+                    )
+                )
                 return None
             timeslots = response.json()
         except Exception as e:
-            self.logger.critical(SU.red("Error while requesting timeslots from Steering!"), e)
+            self.logger.critical(
+                SU.red("Error while requesting timeslots from Steering!"), e
+            )
 
         if not timeslots:
             self.logger.error(SU.red("Got no timeslots via Playout API (Steering)!"))
@@ -180,8 +186,6 @@ class ApiFetcher(threading.Thread):
 
         return self.polish_timeslots(timeslots)
 
-
-
     def fetch_playlists(self):
         """
         Fetches all playlists including fallback playlists for every timeslot.
@@ -189,32 +193,50 @@ class ApiFetcher(threading.Thread):
         over and extend timeslot data.
         """
         # store fetched entries => do not have to fetch playlist_id more than once
-        fetched_entries=[]
+        fetched_entries = []
 
         try:
             for timeslot in self.fetched_timeslot_data:
 
                 # Get IDs of specific, default and fallback playlists
                 playlist_id = self.get_playlist_id(timeslot, "playlist_id")
-                default_schedule_playlist_id = self.get_playlist_id(timeslot, "default_schedule_playlist_id")
-                default_show_playlist_id = self.get_playlist_id(timeslot, "default_show_playlist_id")
-                schedule_fallback_id = self.get_playlist_id(timeslot, "schedule_fallback_id")
+                default_schedule_playlist_id = self.get_playlist_id(
+                    timeslot, "default_schedule_playlist_id"
+                )
+                default_show_playlist_id = self.get_playlist_id(
+                    timeslot, "default_show_playlist_id"
+                )
+                schedule_fallback_id = self.get_playlist_id(
+                    timeslot, "schedule_fallback_id"
+                )
                 show_fallback_id = self.get_playlist_id(timeslot, "show_fallback_id")
-                station_fallback_id = self.get_playlist_id(timeslot, "station_fallback_id")
+                station_fallback_id = self.get_playlist_id(
+                    timeslot, "station_fallback_id"
+                )
 
                 # Retrieve playlist, default and the fallback playlists for every timeslot.
                 # If a playlist (like station_fallback) is already fetched, it is not fetched again but reused
                 timeslot["playlist"] = self.fetch_playlist(playlist_id, fetched_entries)
-                timeslot["default_schedule_playlist"] = self.fetch_playlist(default_schedule_playlist_id, fetched_entries)
-                timeslot["default_show_playlist"] = self.fetch_playlist(default_show_playlist_id, fetched_entries)
-                timeslot["schedule_fallback"] = self.fetch_playlist(schedule_fallback_id, fetched_entries)
-                timeslot["show_fallback"] = self.fetch_playlist(show_fallback_id, fetched_entries)
-                timeslot["station_fallback"]  = self.fetch_playlist(station_fallback_id, fetched_entries)
+                timeslot["default_schedule_playlist"] = self.fetch_playlist(
+                    default_schedule_playlist_id, fetched_entries
+                )
+                timeslot["default_show_playlist"] = self.fetch_playlist(
+                    default_show_playlist_id, fetched_entries
+                )
+                timeslot["schedule_fallback"] = self.fetch_playlist(
+                    schedule_fallback_id, fetched_entries
+                )
+                timeslot["show_fallback"] = self.fetch_playlist(
+                    show_fallback_id, fetched_entries
+                )
+                timeslot["station_fallback"] = self.fetch_playlist(
+                    station_fallback_id, fetched_entries
+                )
 
         except Exception as e:
-            self.logger.error("Error while fetching playlists from API endpoints: " + str(e), e)
-
-
+            self.logger.error(
+                "Error while fetching playlists from API endpoints: " + str(e), e
+            )
 
     def fetch_playlist(self, playlist_id, fetched_playlists):
         """
@@ -234,7 +256,7 @@ class ApiFetcher(threading.Thread):
         url = self.tank_playlist_url.replace("${ID}", playlist_id)
         headers = {
             "Authorization": "Bearer %s:%s" % (self.tank_session, self.tank_secret),
-            "content-type": "application/json"
+            "content-type": "application/json",
         }
 
         # If playlist is already fetched in this round, use the existing one
@@ -247,19 +269,26 @@ class ApiFetcher(threading.Thread):
             self.logger.debug("Fetch playlist from Tank API...")
             response = requests.get(url, data=None, headers=headers)
             if not response.status_code == 200:
-                self.logger.critical(SU.red("HTTP Status: %s | Playlist #%s could not be fetched or is not available! Response: %s" % \
-                    (str(response.status_code), str(playlist_id), response.text)))
+                self.logger.critical(
+                    SU.red(
+                        "HTTP Status: %s | Playlist #%s could not be fetched or is not available! Response: %s"
+                        % (str(response.status_code), str(playlist_id), response.text)
+                    )
+                )
                 return None
             playlist = response.json()
         except Exception as e:
-            self.logger.critical(SU.red("Error while requesting playlist #%s from Tank" % str(playlist_id)), e)
+            self.logger.critical(
+                SU.red(
+                    "Error while requesting playlist #%s from Tank" % str(playlist_id)
+                ),
+                e,
+            )
             return None
 
         fetched_playlists.append(playlist)
         return playlist
 
-
-
     def get_playlist_id(self, timeslot, id_name):
         """
         Extracts the playlist ID for a given playlist (fallback) type.
@@ -276,13 +305,14 @@ class ApiFetcher(threading.Thread):
 
         playlist_id = str(timeslot[id_name])
         if not playlist_id or playlist_id == "None":
-            self.logger.debug("No value defined for '%s' in timeslot '#%s'" % (id_name, timeslot["id"]))
+            self.logger.debug(
+                "No value defined for '%s' in timeslot '#%s'"
+                % (id_name, timeslot["id"])
+            )
             return None
 
         return playlist_id
 
-
-
     def polish_timeslots(self, timeslots):
         """
         Removes all timeslots which are not relevant for further processing,
@@ -292,14 +322,15 @@ class ApiFetcher(threading.Thread):
         timeslots = TimeslotFilter.filter_24h(timeslots)
         timeslots = TimeslotFilter.filter_past(timeslots)
         count_after = len(timeslots)
-        self.logger.debug("Removed %d unnecessary timeslots from response. Timeslots left: %d" % ((count_before - count_after), count_after))
+        self.logger.debug(
+            "Removed %d unnecessary timeslots from response. Timeslots left: %d"
+            % ((count_before - count_after), count_after)
+        )
 
         for t in timeslots:
             t["timeslot_id"] = t["id"]
         return timeslots
 
-
-
     def terminate(self):
         """
         Terminates the thread.
diff --git a/src/scheduling/models.py b/src/scheduling/models.py
index 9b3baf67..adf87160 100644
--- a/src/scheduling/models.py
+++ b/src/scheduling/models.py
@@ -289,7 +289,6 @@ class Timeslot(DB.Model, AuraDatabaseModel):
         )
         return timeslots
 
-
     @hybrid_property
     def start_unix(self):
         """
diff --git a/src/scheduling/programme.py b/src/scheduling/programme.py
index 32614a9b..c44ed3cd 100644
--- a/src/scheduling/programme.py
+++ b/src/scheduling/programme.py
@@ -1,5 +1,3 @@
-
-
 #
 # Aura Engine (https://gitlab.servus.at/aura/engine)
 #
@@ -19,7 +17,6 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-
 import logging
 
 from datetime import datetime
@@ -29,15 +26,20 @@ from src.base.config import AuraConfig
 from src.base.utils import SimpleUtil as SU
 from src.engine import Engine
 from src.scheduling.utils import M3UPlaylistProcessor
-from src.scheduling.models import Timeslot, Playlist, PlaylistEntry, PlaylistEntryMetaData
+from src.scheduling.models import (
+    Timeslot,
+    Playlist,
+    PlaylistEntry,
+    PlaylistEntryMetaData,
+)
 from src.scheduling.api import ApiFetcher
 
 
-
-class ProgrammeService():
+class ProgrammeService:
     """
     The current programme of the calendar. The programme is a set of timeslots for the current day.
     """
+
     config = None
     logger = None
     timeslots = None
@@ -45,7 +47,6 @@ class ProgrammeService():
     last_successful_fetch = None
     programme_store = None
 
-
     def __init__(self):
         """
         Constructor
@@ -54,8 +55,6 @@ class ProgrammeService():
         self.logger = logging.getLogger("AuraEngine")
         self.programme_store = ProgrammeStore()
 
-
-
     def refresh(self):
         """
         Fetch the latest programme from `ProgrammeStore` which stores it to the database.
@@ -71,31 +70,51 @@ class ProgrammeService():
         response = self.api_fetcher.get_fetched_data()
 
         if response is None:
-            msg = SU.red("Trying to load programme from Engine Database, because ApiFetcher returned an empty response.")
+            msg = SU.red(
+                "Trying to load programme from Engine Database, because ApiFetcher returned an empty response."
+            )
             self.logger.warning(msg)
         elif type(response) is list:
             if len(response) > 0:
                 self.last_successful_fetch = datetime.now()
                 self.timeslots = self.programme_store.store_timeslots(response)
-                self.logger.info(SU.green(f"Finished fetching current programme from API ({len(response)} timeslots)"))
+                self.logger.info(
+                    SU.green(
+                        f"Finished fetching current programme from API ({len(response)} timeslots)"
+                    )
+                )
             else:
-                self.logger.critical("Programme fetched from Steering/Tank has no entries!")
+                self.logger.critical(
+                    "Programme fetched from Steering/Tank has no entries!"
+                )
         elif response.startswith("fetching_aborted"):
-            msg = SU.red("Load programme from DB, because fetching was aborted by ApiFetcher! Reason: " + response[17:])
+            msg = SU.red(
+                "Load programme from DB, because fetching was aborted by ApiFetcher! Reason: "
+                + response[17:]
+            )
             self.logger.warning(msg)
         else:
-            msg = SU.red("Load programme from DB, because of an unknown response from ApiFetcher: " + response)
+            msg = SU.red(
+                "Load programme from DB, because of an unknown response from ApiFetcher: "
+                + response
+            )
             self.logger.warning(msg)
 
         # Load latest programme from the database
         if not self.timeslots:
             self.timeslots = self.programme_store.load_timeslots()
-            self.logger.info(SU.green("Finished loading current programme from database (%s timeslots)" % str(len(self.timeslots))))
+            self.logger.info(
+                SU.green(
+                    "Finished loading current programme from database (%s timeslots)"
+                    % str(len(self.timeslots))
+                )
+            )
 
         for timeslot in self.timeslots:
-            self.logger.debug("\tTimeslot %s with Playlist %s" % (str(timeslot), str(timeslot.playlist)))
-
-
+            self.logger.debug(
+                "\tTimeslot %s with Playlist %s"
+                % (str(timeslot), str(timeslot.playlist))
+            )
 
     def get_current_entry(self):
         """
@@ -138,8 +157,6 @@ class ProgrammeService():
 
         return current_entry
 
-
-
     def get_current_timeslot(self):
         """
         Retrieves the timeslot currently to be played.
@@ -159,8 +176,6 @@ class ProgrammeService():
 
         return current_timeslot
 
-
-
     def get_current_playlist(self, timeslot):
         """
         Retrieves the playlist to be scheduled. If no specific playlist is assigned,
@@ -181,8 +196,6 @@ class ProgrammeService():
 
         return (playlist_type, playlist)
 
-
-
     def get_next_timeslots(self, max_count=0):
         """
         Retrieves the timeslots to be played after the current one.
@@ -208,8 +221,6 @@ class ProgrammeService():
 
         return next_timeslots
 
-
-
     def is_timeslot_in_window(self, timeslot):
         """
         Checks if the timeslot is within the scheduling window.
@@ -218,14 +229,14 @@ class ProgrammeService():
         window_start = self.config.get("scheduling_window_start")
         window_end = self.config.get("scheduling_window_end")
 
-        if timeslot.start_unix - window_start < now_unix and \
-            timeslot.start_unix - window_end > now_unix:
+        if (
+            timeslot.start_unix - window_start < now_unix
+            and timeslot.start_unix - window_end > now_unix
+        ):
 
             return True
         return False
 
-
-
     def terminate(self):
         """
         Called when thread is stopped or a signal to terminate is received.
@@ -235,9 +246,7 @@ class ProgrammeService():
             self.api_fetcher.terminate()
 
 
-
-
-class ProgrammeStore():
+class ProgrammeStore:
     """
     The `ProgrammeStore` service retrieves all current schedules and related
     playlists including audio files from the configured API endpoints and stores
@@ -245,6 +254,7 @@ class ProgrammeStore():
 
     To perform the API queries it utilizes the ApiFetcher class.
     """
+
     config = None
     logger = None
     m3u_processor = None
@@ -257,7 +267,6 @@ class ProgrammeStore():
         self.logger = logging.getLogger("AuraEngine")
         self.m3u_processor = M3UPlaylistProcessor()
 
-
     def load_timeslots(self):
         """
         Loads the programme from the database.
@@ -266,11 +275,14 @@ class ProgrammeStore():
         try:
             timeslots = Timeslot.get_timeslots(datetime.now())
         except Exception as e:
-            self.logger.critical(SU.red("Could not load programme from database. We are in big trouble my friend!"), e)
+            self.logger.critical(
+                SU.red(
+                    "Could not load programme from database. We are in big trouble my friend!"
+                ),
+                e,
+            )
         return timeslots
 
-
-
     def store_timeslots(self, fetched_timeslots):
         """
         Stores the fetched timeslots to the database.
@@ -285,10 +297,16 @@ class ProgrammeStore():
 
             # Check timeslot for validity
             if "start" not in timeslot:
-                self.logger.warning("No 'start' of timeslot given. Skipping the timeslot: %s " % str(timeslot))
+                self.logger.warning(
+                    "No 'start' of timeslot given. Skipping the timeslot: %s "
+                    % str(timeslot)
+                )
                 continue
             if "end" not in timeslot:
-                self.logger.warning("No 'end' of timeslot given. Skipping the timeslot: %s " % str(timeslot))
+                self.logger.warning(
+                    "No 'end' of timeslot given. Skipping the timeslot: %s "
+                    % str(timeslot)
+                )
                 continue
 
             # Store the timeslot
@@ -296,22 +314,40 @@ class ProgrammeStore():
             timeslots.append(timeslot_db)
 
             # Store assigned playlists
-            self.store_playlist(timeslot_db, timeslot_db.playlist_id, timeslot["playlist"])
+            self.store_playlist(
+                timeslot_db, timeslot_db.playlist_id, timeslot["playlist"]
+            )
             if timeslot_db.default_schedule_playlist_id:
-                self.store_playlist(timeslot_db, timeslot_db.default_schedule_playlist_id, timeslot["default_schedule_playlist"])
+                self.store_playlist(
+                    timeslot_db,
+                    timeslot_db.default_schedule_playlist_id,
+                    timeslot["default_schedule_playlist"],
+                )
             if timeslot_db.default_show_playlist_id:
-                self.store_playlist(timeslot_db, timeslot_db.default_show_playlist_id, timeslot["default_show_playlist"])
+                self.store_playlist(
+                    timeslot_db,
+                    timeslot_db.default_show_playlist_id,
+                    timeslot["default_show_playlist"],
+                )
             if timeslot_db.schedule_fallback_id:
-                self.store_playlist(timeslot_db, timeslot_db.schedule_fallback_id, timeslot["schedule_fallback"])
+                self.store_playlist(
+                    timeslot_db,
+                    timeslot_db.schedule_fallback_id,
+                    timeslot["schedule_fallback"],
+                )
             if timeslot_db.show_fallback_id:
-                self.store_playlist(timeslot_db, timeslot_db.show_fallback_id, timeslot["show_fallback"])
+                self.store_playlist(
+                    timeslot_db, timeslot_db.show_fallback_id, timeslot["show_fallback"]
+                )
             if timeslot_db.station_fallback_id:
-                self.store_playlist(timeslot_db, timeslot_db.station_fallback_id, timeslot["station_fallback"])
+                self.store_playlist(
+                    timeslot_db,
+                    timeslot_db.station_fallback_id,
+                    timeslot["station_fallback"],
+                )
 
         return timeslots
 
-
-
     def update_deleted_timeslots(self, fetched_timeslots):
         """
         Checks if some timeslot has been deleted remotely, so delete it locally too.
@@ -335,22 +371,33 @@ class ProgrammeStore():
             if local_timeslot.start_unix > now_unix:
 
                 # Filter the local timeslot from the fetched ones
-                existing_remotely = list(filter(lambda new_timeslot: \
-                    new_timeslot["timeslot_id"] == local_timeslot.timeslot_id, fetched_timeslots))
+                existing_remotely = list(
+                    filter(
+                        lambda new_timeslot: new_timeslot["timeslot_id"]
+                        == local_timeslot.timeslot_id,
+                        fetched_timeslots,
+                    )
+                )
 
                 if not existing_remotely:
                     # Only allow deletion of timeslots which are deleted before the start of the scheduling window
                     if (local_timeslot.start_unix - scheduling_window_start) > now_unix:
-                        self.logger.info("Timeslot #%s has been deleted remotely, hence also delete it locally too [%s]" % \
-                            (local_timeslot.timeslot_id, str(local_timeslot)))
+                        self.logger.info(
+                            "Timeslot #%s has been deleted remotely, hence also delete it locally too [%s]"
+                            % (local_timeslot.timeslot_id, str(local_timeslot))
+                        )
                         local_timeslot.delete(commit=True)
-                        self.logger.info("Remotely deleted timeslot #%s from local database" % local_timeslot.timeslot_id)
+                        self.logger.info(
+                            "Remotely deleted timeslot #%s from local database"
+                            % local_timeslot.timeslot_id
+                        )
                     else:
-                        msg = "Timeslot #%s has been deleted remotely. Since the scheduling window has already started, it won't be deleted locally." % local_timeslot.timeslot_id
+                        msg = (
+                            "Timeslot #%s has been deleted remotely. Since the scheduling window has already started, it won't be deleted locally."
+                            % local_timeslot.timeslot_id
+                        )
                         self.logger.error(SU.red(msg))
 
-
-
     def store_timeslot(self, timeslot):
         """
         Stores the given timeslot to the database.
@@ -363,7 +410,9 @@ class ProgrammeStore():
         havetoadd = False
 
         if not timeslot_db:
-            self.logger.debug("no timeslot with given timeslot id in database => create new")
+            self.logger.debug(
+                "no timeslot with given timeslot id in database => create new"
+            )
             timeslot_db = Timeslot()
             havetoadd = True
 
@@ -384,7 +433,9 @@ class ProgrammeStore():
 
         # Optional API properties
         if "default_schedule_playlist_id" in timeslot:
-            timeslot_db.default_schedule_playlist_id = timeslot["default_schedule_playlist_id"]
+            timeslot_db.default_schedule_playlist_id = timeslot[
+                "default_schedule_playlist_id"
+            ]
         if "default_show_playlist_id" in timeslot:
             timeslot_db.default_show_playlist_id = timeslot["default_show_playlist_id"]
         if "schedule_fallback_id" in timeslot:
@@ -394,12 +445,14 @@ class ProgrammeStore():
         if "station_fallback_id" in timeslot:
             timeslot_db.station_fallback_id = timeslot["station_fallback_id"]
 
-        self.logger.debug(SU.pink(f"Store/Update TIMESLOT havetoadd={havetoadd} - data: "+str(timeslot)))
+        self.logger.debug(
+            SU.pink(
+                f"Store/Update TIMESLOT havetoadd={havetoadd} - data: " + str(timeslot)
+            )
+        )
         timeslot_db.store(add=havetoadd, commit=True)
         return timeslot_db
 
-
-
     def store_playlist(self, timeslot_db, playlist_id, fetched_playlist):
         """
         Stores the Playlist to the database.
@@ -408,14 +461,18 @@ class ProgrammeStore():
             self.logger.debug(f"Playlist ID#{playlist_id} is not available!")
             return
 
-        playlist_db = Playlist.select_playlist_for_timeslot(timeslot_db.timeslot_start, playlist_id)
+        playlist_db = Playlist.select_playlist_for_timeslot(
+            timeslot_db.timeslot_start, playlist_id
+        )
         havetoadd = False
 
         if not playlist_db:
             playlist_db = Playlist()
             havetoadd = True
 
-        self.logger.debug("Storing playlist %d for timeslot (%s)" % (playlist_id, str(timeslot_db)))
+        self.logger.debug(
+            "Storing playlist %d for timeslot (%s)" % (playlist_id, str(timeslot_db))
+        )
         playlist_db.playlist_id = playlist_id
         playlist_db.timeslot_start = timeslot_db.timeslot_start
         playlist_db.show_name = timeslot_db.show_name
@@ -431,8 +488,6 @@ class ProgrammeStore():
 
         return playlist_db
 
-
-
     def store_playlist_entries(self, timeslot_db, playlist_db, fetched_playlist):
         """
         Stores the playlist entries to the database.
@@ -450,7 +505,9 @@ class ProgrammeStore():
         self.delete_orphaned_entries(playlist_db, entries)
 
         for entry in entries:
-            entry_db = PlaylistEntry.select_playlistentry_for_playlist(playlist_db.artificial_id, entry_num)
+            entry_db = PlaylistEntry.select_playlistentry_for_playlist(
+                playlist_db.artificial_id, entry_num
+            )
             havetoadd = False
             if not entry_db:
                 entry_db = PlaylistEntry()
@@ -474,8 +531,6 @@ class ProgrammeStore():
             entry_num = entry_num + 1
             time_marker += entry_db.duration
 
-
-
     def delete_orphaned_entries(self, playlist_db, entries):
         """
         Deletes all playlist entries which are beyond the current playlist's `entry_count`.
@@ -483,25 +538,30 @@ class ProgrammeStore():
         less entries than before.
         """
         new_last_idx = len(entries)
-        existing_last_idx = PlaylistEntry.count_entries(playlist_db.artificial_id)-1
+        existing_last_idx = PlaylistEntry.count_entries(playlist_db.artificial_id) - 1
 
         if existing_last_idx < new_last_idx:
             return
 
-        for entry_num in range(new_last_idx, existing_last_idx+1, 1):
+        for entry_num in range(new_last_idx, existing_last_idx + 1, 1):
             PlaylistEntry.delete_entry(playlist_db.artificial_id, entry_num)
-            self.logger.info(SU.yellow("Deleted playlist entry %s:%s" % (playlist_db.artificial_id, entry_num)))
+            self.logger.info(
+                SU.yellow(
+                    "Deleted playlist entry %s:%s"
+                    % (playlist_db.artificial_id, entry_num)
+                )
+            )
             entry_num += 1
 
-
-
     def expand_entry_duration(self, timeslot_db, entries):
         """
         If some playlist entry doesn't have a duration assigned, its duration is expanded to the
         remaining duration of the playlist (= timeslot duration minus playlist entries with duration).
         If there's more than one entry without duration, such entries are removed from the playlist.
         """
-        total_seconds = (timeslot_db.timeslot_end - timeslot_db.timeslot_start).total_seconds()
+        total_seconds = (
+            timeslot_db.timeslot_end - timeslot_db.timeslot_start
+        ).total_seconds()
         total_duration = SU.seconds_to_nano(total_seconds)
         actual_duration = 0
         missing_duration = []
@@ -516,22 +576,27 @@ class ProgrammeStore():
 
         if len(missing_duration) == 1:
             entries[missing_duration[0]]["duration"] = total_duration - actual_duration
-            self.logger.info(f"Expanded duration of playlist entry #{missing_duration[0]}")
+            self.logger.info(
+                f"Expanded duration of playlist entry #{missing_duration[0]}"
+            )
 
         elif len(missing_duration) > 1:
             # This case should actually never happen, as TANK doesn't allow more than one entry w/o duration anymore
             for i in reversed(missing_duration[1:-1]):
-                self.logger.error(SU.red("Deleted Playlist Entry without duration: %s" % \
-                    str(entries[i])))
+                self.logger.error(
+                    SU.red(
+                        "Deleted Playlist Entry without duration: %s" % str(entries[i])
+                    )
+                )
                 del entries[i]
 
-
-
     def store_playlist_entry_metadata(self, entry_db, metadata):
         """
         Stores the meta-data for a PlaylistEntry.
         """
-        metadata_db = PlaylistEntryMetaData.select_metadata_for_entry(entry_db.artificial_id)
+        metadata_db = PlaylistEntryMetaData.select_metadata_for_entry(
+            entry_db.artificial_id
+        )
         havetoadd = False
         if not metadata_db:
             metadata_db = PlaylistEntryMetaData()
@@ -555,6 +620,3 @@ class ProgrammeStore():
             metadata_db.title = ""
 
         metadata_db.store(havetoadd, commit=True)
-
-
-
diff --git a/src/scheduling/scheduler.py b/src/scheduling/scheduler.py
index 0fae8bad..966441e9 100644
--- a/src/scheduling/scheduler.py
+++ b/src/scheduling/scheduler.py
@@ -147,7 +147,6 @@ class AuraScheduler(threading.Thread):
 
         # Nothing to do atm
 
-
     #
     #   METHODS
     #
@@ -447,7 +446,11 @@ class TimeslotCommand(EngineExecutor):
         # Initialize the "fade in" EngineExecuter and instantiate a connected child EngineExecuter for "fade out" when the parent is ready
         timeslot_id = timeslot.timeslot_id
         super().__init__(
-            f"TIMESLOT#{timeslot_id}", None, timeslot.start_unix, self.do_start_timeslot, timeslot
+            f"TIMESLOT#{timeslot_id}",
+            None,
+            timeslot.start_unix,
+            self.do_start_timeslot,
+            timeslot,
         )
         EngineExecutor(
             f"TIMESLOT#{timeslot_id}",
@@ -505,7 +508,9 @@ class PlayCommand(EngineExecutor):
         self.logger.info(msg)
         # Initialize the "preload" EngineExecuter and attach a child `PlayCommand` to the "on_ready" event handler
         timeslot_id = entries[0].playlist.timeslot.timeslot_id
-        super().__init__(f"PRELOAD#{timeslot_id}", None, start_preload, self.do_preload, entries)
+        super().__init__(
+            f"PRELOAD#{timeslot_id}", None, start_preload, self.do_preload, entries
+        )
         EngineExecutor(f"PLAY#{timeslot_id}", self, start_play, self.do_play, entries)
 
     def do_preload(self, entries):
diff --git a/src/scheduling/utils.py b/src/scheduling/utils.py
index 8f11c918..210150ed 100644
--- a/src/scheduling/utils.py
+++ b/src/scheduling/utils.py
@@ -1,4 +1,3 @@
-
 #
 # Aura Engine (https://gitlab.servus.at/aura/engine)
 #
@@ -27,20 +26,17 @@ from src.base.utils import SimpleUtil as SU
 from src.base.config import AuraConfig
 
 
-
 class EntryQueueState(Enum):
     """
     Types of playlist entry behaviours.
     """
+
     OKAY = "ok"
     CUT = "cut"
     OUT_OF_SCHEDULE = "oos"
 
 
-
-
-
-class TimeslotFilter():
+class TimeslotFilter:
     """
     Filters timeslot dictionaries with various criteria.
     """
@@ -55,8 +51,8 @@ class TimeslotFilter():
         """
         items = []
         now = SU.timestamp()
-        now_plus_24hours = now + (12*60*60)
-        now_minus_12hours = now - (12*60*60)
+        now_plus_24hours = now + (12 * 60 * 60)
+        now_minus_12hours = now - (12 * 60 * 60)
 
         for s in timeslots:
             start_time = datetime.strptime(s["start"], "%Y-%m-%dT%H:%M:%S")
@@ -67,7 +63,6 @@ class TimeslotFilter():
 
         return items
 
-
     @staticmethod
     def filter_past(timeslots):
         """
@@ -85,23 +80,22 @@ class TimeslotFilter():
             # Append all elements in the future
             if start_time >= now:
                 items.append(s)
-             # Append the one which is playing now
+            # Append the one which is playing now
             elif start_time < now < end_time:
                 items.append(s)
 
         return items
 
 
-
-class M3UPlaylistProcessor():
+class M3UPlaylistProcessor:
     """
     Renders a M3U Playlist as a engine compatible playlist dictionary.
     """
+
     config = None
     logging = None
     playlist_folder = None
 
-
     def __init__(self):
         """
         Constructor
@@ -110,8 +104,6 @@ class M3UPlaylistProcessor():
         self.logger = logging.getLogger("AuraEngine")
         self.playlist_folder = self.config.abs_playlist_path()
 
-
-
     def spread(self, entries):
         """
         Converts a playlist with M3U entries and renders them as individual playlist entries.
@@ -136,8 +128,6 @@ class M3UPlaylistProcessor():
 
         return result
 
-
-
     def read_m3u_file(self, source_file):
         """
         Read entries from an M3U file.
@@ -154,11 +144,11 @@ class M3UPlaylistProcessor():
                         "metadata": {
                             "artist": metadata[1].split(" - ")[0],
                             "album": "",
-                            "title": metadata[1].split(" - ")[1]
+                            "title": metadata[1].split(" - ")[1],
                         }
                     },
                     "duration": SU.seconds_to_nano(int(metadata[0])),
-                    "uri": "file://" + lines[i+1]
+                    "uri": "file://" + lines[i + 1],
                 }
                 entries.append(entry)
 
@@ -166,16 +156,15 @@ class M3UPlaylistProcessor():
         return entries
 
 
-
 class TimeslotRenderer:
     """
     Displays current and next timeslots in ASCII for maintenance and debugging.
     """
+
     logger = None
     scheduler = None
     programme = None
 
-
     def __init__(self, scheduler):
         """
         Constructor
@@ -184,7 +173,6 @@ class TimeslotRenderer:
         self.scheduler = scheduler
         self.programme = scheduler.get_programme()
 
-
     def get_ascii_timeslots(self):
         """
         Creates a printable version of the current programme (playlists and entries as per timeslot)
@@ -201,19 +189,28 @@ class TimeslotRenderer:
             if active_timeslot.playlist:
                 planned_playlist = active_timeslot.playlist
 
-            (playlist_type, resolved_playlist) = self.scheduler.resolve_playlist(active_timeslot)
+            (playlist_type, resolved_playlist) = self.scheduler.resolve_playlist(
+                active_timeslot
+            )
 
             s += "\n│   Playing timeslot %s         " % active_timeslot
             if planned_playlist:
-                if resolved_playlist and resolved_playlist.playlist_id != planned_playlist.playlist_id:
+                if (
+                    resolved_playlist
+                    and resolved_playlist.playlist_id != planned_playlist.playlist_id
+                ):
                     s += "\n│       └── Playlist %s         " % planned_playlist
                     s += "\n│       "
-                    s += SU.red("↑↑↑ That's the originally planned playlist.") + ("Instead playing the default playlist below ↓↓↓")
+                    s += SU.red("↑↑↑ That's the originally planned playlist.") + (
+                        "Instead playing the default playlist below ↓↓↓"
+                    )
 
             if resolved_playlist:
                 if not planned_playlist:
                     s += "\n│      "
-                    s += SU.red(f"No playlist assigned to timeslot. Instead playing the '{playlist_type.get('name')}' playlist below ↓↓↓")
+                    s += SU.red(
+                        f"No playlist assigned to timeslot. Instead playing the '{playlist_type.get('name')}' playlist below ↓↓↓"
+                    )
 
                 s += "\n│       └── Playlist %s         " % resolved_playlist
 
@@ -228,8 +225,10 @@ class TimeslotRenderer:
 
                 # Entry currently being played
                 if active_entry:
-                    s += "\n│         └── Entry %s | %s         " % \
-                        (str(active_entry.entry_num+1), SU.green("PLAYING > "+str(active_entry)))
+                    s += "\n│         └── Entry %s | %s         " % (
+                        str(active_entry.entry_num + 1),
+                        SU.green("PLAYING > " + str(active_entry)),
+                    )
 
                     # Open entries for current playlist
                     rest_of_playlist = active_entry.get_next_entries(False)
@@ -237,7 +236,11 @@ class TimeslotRenderer:
                     s += self.build_playlist_string(entries)
 
             else:
-                s += "\n│       └── %s" % (SU.red("No active playlist. There should be at least some fallback playlist running..."))
+                s += "\n│       └── %s" % (
+                    SU.red(
+                        "No active playlist. There should be at least some fallback playlist running..."
+                    )
+                )
         else:
             s += "\n│   Nothing.           "
         s += "\n└──────────────────────────────────────────────────────────────────────────────────────────────────────"
@@ -250,14 +253,23 @@ class TimeslotRenderer:
             s += "\n│   Nothing.         "
         else:
             for timeslot in next_timeslots:
-                (playlist_type, resolved_playlist) = self.scheduler.resolve_playlist(timeslot)
+                (playlist_type, resolved_playlist) = self.scheduler.resolve_playlist(
+                    timeslot
+                )
                 if resolved_playlist:
 
                     s += "\n│   Queued timeslot %s         " % timeslot
-                    s += "\n│      └── Playlist %s         (Type: %s)" % (resolved_playlist, SU.cyan(str(playlist_type)))
+                    s += "\n│      └── Playlist %s         (Type: %s)" % (
+                        resolved_playlist,
+                        SU.cyan(str(playlist_type)),
+                    )
                     if resolved_playlist.end_unix > timeslot.end_unix:
-                        s += "\n│          %s!              " % \
-                        (SU.red("↑↑↑ Playlist #%s ends after timeslot #%s!" % (resolved_playlist.playlist_id, timeslot.timeslot_id)))
+                        s += "\n│          %s!              " % (
+                            SU.red(
+                                "↑↑↑ Playlist #%s ends after timeslot #%s!"
+                                % (resolved_playlist.playlist_id, timeslot.timeslot_id)
+                            )
+                        )
 
                     entries = self.preprocess_entries(resolved_playlist.entries, False)
                     s += self.build_playlist_string(entries)
@@ -265,8 +277,6 @@ class TimeslotRenderer:
         s += "\n└──────────────────────────────────────────────────────────────────────────────────────────────────────\n\n"
         return s
 
-
-
     def build_playlist_string(self, entries):
         """
         Returns a stringified list of entries
@@ -275,17 +285,19 @@ class TimeslotRenderer:
         is_out_of_timeslot = False
 
         for entry in entries:
-            if entry.queue_state == EntryQueueState.OUT_OF_SCHEDULE and not is_out_of_timeslot:
-                s += "\n│             %s" % \
-                    SU.red("↓↓↓ These entries won't be played because they are out of timeslot.")
+            if (
+                entry.queue_state == EntryQueueState.OUT_OF_SCHEDULE
+                and not is_out_of_timeslot
+            ):
+                s += "\n│             %s" % SU.red(
+                    "↓↓↓ These entries won't be played because they are out of timeslot."
+                )
                 is_out_of_timeslot = True
 
             s += self.build_entry_string("\n│         └── ", entry, is_out_of_timeslot)
 
         return s
 
-
-
     def build_entry_string(self, prefix, entry, strike):
         """
         Returns an stringified entry.
@@ -299,11 +311,9 @@ class TimeslotRenderer:
         else:
             entry_str = str(entry)
 
-        entry_line = "%sEntry %s | %s" % (prefix, str(entry.entry_num+1), entry_str)
+        entry_line = "%sEntry %s | %s" % (prefix, str(entry.entry_num + 1), entry_str)
         return s + entry_line
 
-
-
     def preprocess_entries(self, entries, cut_oos):
         """
         Analyses and marks entries which are going to be cut or excluded.
@@ -320,7 +330,10 @@ class TimeslotRenderer:
         for entry in entries:
 
             if entry.entry_start >= entry.playlist.timeslot.timeslot_end:
-                msg = "Filtered entry (%s) after end-of timeslot (%s) ... SKIPPED" % (entry, entry.playlist.timeslot)
+                msg = "Filtered entry (%s) after end-of timeslot (%s) ... SKIPPED" % (
+                    entry,
+                    entry.playlist.timeslot,
+                )
                 self.logger.debug(msg)
                 entry.queue_state = EntryQueueState.OUT_OF_SCHEDULE
             elif entry.end_unix > entry.playlist.timeslot.end_unix:
@@ -331,4 +344,4 @@ class TimeslotRenderer:
             if not entry.queue_state == EntryQueueState.OUT_OF_SCHEDULE or not cut_oos:
                 clean_entries.append(entry)
 
-        return clean_entries
\ No newline at end of file
+        return clean_entries
diff --git a/tests/test_config.py b/tests/test_config.py
index 392ebc46..a1dfcc16 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1,4 +1,3 @@
-
 #
 # Aura Engine (https://gitlab.servus.at/aura/engine)
 #
@@ -26,18 +25,16 @@ from src.base.utils import SimpleUtil as SU
 from src.base.config import AuraConfig
 
 
-
-
 class TestConfig(unittest.TestCase):
     """
     Testing the Configuration.
     """
+
     config = None
 
     def setUp(self):
         self.config = AuraConfig()
 
-
     def test_config(self):
         # Check if config is available
         self.assertIsNotNone(self.config.ini_path)
@@ -54,7 +51,9 @@ class TestConfig(unittest.TestCase):
         self.assertTrue(validators.url(self.config.get("api_engine_status")))
         self.assertTrue(validators.url(self.config.get("api_engine_store_playlog")))
         self.assertTrue(validators.url(self.config.get("api_engine_store_clock")))
-        engine_health_url = self.config.get("api_engine_store_health").replace("${ENGINE_NUMBER}", "1")
+        engine_health_url = self.config.get("api_engine_store_health").replace(
+            "${ENGINE_NUMBER}", "1"
+        )
         self.assertTrue(validators.url(engine_health_url))
 
         # Check if Liquidsoap "socket_dir" is set and a directory
@@ -67,7 +66,5 @@ class TestConfig(unittest.TestCase):
         self.assertIsNotNone(self.config.get("db_host"))
 
 
-
-
-if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/test_engine_executor.py b/tests/test_engine_executor.py
index e5034a2a..841d2436 100644
--- a/tests/test_engine_executor.py
+++ b/tests/test_engine_executor.py
@@ -1,4 +1,3 @@
-
 #
 # Aura Engine (https://gitlab.servus.at/aura/engine)
 #
@@ -26,8 +25,6 @@ from src.base.utils import SimpleUtil as SU
 from src.control import EngineExecutor
 
 
-
-
 class TestEngineExecutor(unittest.TestCase):
     """
     Testing the EngineExecutor.
@@ -36,13 +33,12 @@ class TestEngineExecutor(unittest.TestCase):
     def setUp(self):
         None
 
-
-
     def test_single_executor(self):
         # Initialize state and executor params
         EngineExecutor.timer_store = {}
         global_state = ["none"]
         due_time = SU.timestamp() + 0.3
+
         def f(param):
             global_state[0] = param
 
@@ -55,24 +51,27 @@ class TestEngineExecutor(unittest.TestCase):
         time.sleep(0.5)
         self.assertEqual("hello world", global_state[0])
 
-
-
     def test_two_executors(self):
         # Initialize state and executor params
         EngineExecutor.timer_store = {}
         global_state = ["none"]
+
         def f(param):
             global_state[0] = param
 
         # Before the executor 1 is done there should be the initial value
         due_time1 = SU.timestamp() + 1
-        e1 = EngineExecutor("EXECUTOR_1", None, due_time1, f, "hello world from executor 1")
+        e1 = EngineExecutor(
+            "EXECUTOR_1", None, due_time1, f, "hello world from executor 1"
+        )
         self.assertEqual("none", global_state[0])
         self.assertNotEqual("hello world from executor 1", global_state[0])
 
         # Before the executor 2 is done there should be still the initial value
         due_time2 = SU.timestamp() + 0.5
-        e2 = EngineExecutor("EXECUTOR_2", None, due_time2, f, "hello world from executor 2")
+        e2 = EngineExecutor(
+            "EXECUTOR_2", None, due_time2, f, "hello world from executor 2"
+        )
         self.assertEqual("none", global_state[0])
         self.assertNotEqual("hello world from executor 2", global_state[0])
 
@@ -88,23 +87,26 @@ class TestEngineExecutor(unittest.TestCase):
         time.sleep(0.5)
         self.assertEqual("hello world from executor 1", global_state[0])
 
-
-
     def test_parent_child_executors_in_order(self):
         # Initialize state and executor params
         EngineExecutor.timer_store = {}
         global_state = ["none"]
+
         def f(param):
             global_state[0] = param
 
         # Before the the parent is done there should be the initial value
         due_time1 = SU.timestamp() + 0.5
-        parent = EngineExecutor("EXECUTOR_PARENT", None, due_time1, f, "hello world from parent")
+        parent = EngineExecutor(
+            "EXECUTOR_PARENT", None, due_time1, f, "hello world from parent"
+        )
         self.assertEqual("none", global_state[0])
 
         # Before the the child is done there should be the initial value
         due_time2 = SU.timestamp() + 1
-        child = EngineExecutor("EXECUTOR_CHILD", parent, due_time2, f, "hello world from child")
+        child = EngineExecutor(
+            "EXECUTOR_CHILD", parent, due_time2, f, "hello world from child"
+        )
         self.assertEqual("none", global_state[0])
 
         # After 0.3 seconds there still should be the initial value
@@ -119,26 +121,28 @@ class TestEngineExecutor(unittest.TestCase):
         time.sleep(1.2)
         self.assertEqual("hello world from child", global_state[0])
 
-
-
     def test_parent_child_executors_with_child_before(self):
         # Initialize state and executor params
         EngineExecutor.timer_store = {}
         global_state = ["none", "never called by parent"]
+
         def f(param):
             global_state[0] = param
             if param == "hello world from parent":
                 global_state[1] = param
 
-
         # Before the the parent is done there should be the initial value
         due_time1 = SU.timestamp() + 0.5
-        parent = EngineExecutor("EXECUTOR_PARENT", None, due_time1, f, "hello world from parent")
+        parent = EngineExecutor(
+            "EXECUTOR_PARENT", None, due_time1, f, "hello world from parent"
+        )
         self.assertEqual("none", global_state[0])
 
         # Before the the child is done there should be the initial value
         due_time2 = SU.timestamp() + 1.5
-        child = EngineExecutor("EXECUTOR_CHILD", parent, due_time2, f, "hello world from child")
+        child = EngineExecutor(
+            "EXECUTOR_CHILD", parent, due_time2, f, "hello world from child"
+        )
         self.assertEqual("none", global_state[0])
 
         # After 0.2 seconds there still should be the initial value
@@ -166,13 +170,11 @@ class TestEngineExecutor(unittest.TestCase):
         # But we do not just believe what we expect, but check if it really has ever been called by a parent
         self.assertEqual("hello world from parent", global_state[1])
 
-
-
-
     def test_timer_store_replacement(self):
         # Initialize state and executor params
         EngineExecutor.timer_store = {}
         global_state = ["none"]
+
         def f(param):
             global_state[0] = param
 
@@ -182,12 +184,16 @@ class TestEngineExecutor(unittest.TestCase):
 
         # Before the the parent is done there should be the initial value
         due_time1 = SU.timestamp() + 0.5
-        parent = EngineExecutor("EXECUTOR_PARENT", None, due_time1, f, "hello world from parent")
+        parent = EngineExecutor(
+            "EXECUTOR_PARENT", None, due_time1, f, "hello world from parent"
+        )
         self.assertEqual("none", global_state[0])
 
         # Before the the child is done there should be the initial value
         due_time2 = SU.timestamp() + 1.5
-        child = EngineExecutor("EXECUTOR_CHILD", parent, due_time2, f, "hello world from child")
+        child = EngineExecutor(
+            "EXECUTOR_CHILD", parent, due_time2, f, "hello world from child"
+        )
         self.assertEqual("none", global_state[0])
 
         # There should be a total of 2 timers
@@ -195,11 +201,15 @@ class TestEngineExecutor(unittest.TestCase):
         self.assertEqual(2, len(timers))
 
         # Replacing the parent with a new instance
-        parent = EngineExecutor("EXECUTOR_PARENT", None, due_time1, f, "hello world from alternative parent")
+        parent = EngineExecutor(
+            "EXECUTOR_PARENT", None, due_time1, f, "hello world from alternative parent"
+        )
         self.assertEqual("none", global_state[0])
 
         # Before the the child is done there should be the initial value
-        child = EngineExecutor("EXECUTOR_CHILD", parent, due_time2, f, "hello world from alternative child")
+        child = EngineExecutor(
+            "EXECUTOR_CHILD", parent, due_time2, f, "hello world from alternative child"
+        )
         self.assertEqual("none", global_state[0])
 
         # After 1 seconds max there should be the updated value from the alternative parent
@@ -214,24 +224,26 @@ class TestEngineExecutor(unittest.TestCase):
         timers = EngineExecutor.command_history()
         self.assertEqual(2, len(timers))
 
-
-
-
     def test_dead_parent_with_lively_child(self):
         # Initialize state and executor params
         EngineExecutor.timer_store = {}
         global_state = ["none"]
+
         def f(param):
             global_state[0] = param
 
         # Before the the parent is done there should be the initial value
         due_time1 = SU.timestamp() + 0.5
-        parent = EngineExecutor("EXECUTOR_PARENT", None, due_time1, f, "hello world from parent")
+        parent = EngineExecutor(
+            "EXECUTOR_PARENT", None, due_time1, f, "hello world from parent"
+        )
         self.assertEqual("none", global_state[0])
 
         # Before the the child is done there should be the initial value
         due_time2 = SU.timestamp() + 1.5
-        child = EngineExecutor("EXECUTOR_CHILD", parent, due_time2, f, "hello world from child")
+        child = EngineExecutor(
+            "EXECUTOR_CHILD", parent, due_time2, f, "hello world from child"
+        )
         self.assertEqual("none", global_state[0])
 
         # Wait until the parent timer got executed
@@ -243,8 +255,12 @@ class TestEngineExecutor(unittest.TestCase):
         self.assertEqual(True, child.is_alive())
 
         # Replacing the parent & child with a new instance
-        parent = EngineExecutor("EXECUTOR_PARENT", None, due_time1, f, "hello world from late parent")
-        child = EngineExecutor("EXECUTOR_PARENT", None, due_time2, f, "hello world from alternative child")
+        parent = EngineExecutor(
+            "EXECUTOR_PARENT", None, due_time1, f, "hello world from late parent"
+        )
+        child = EngineExecutor(
+            "EXECUTOR_PARENT", None, due_time2, f, "hello world from alternative child"
+        )
 
         # New parent = dead before finished initialization already, so actually never born
         self.assertEqual(False, parent.is_alive())
@@ -258,7 +274,5 @@ class TestEngineExecutor(unittest.TestCase):
         self.assertEqual("hello world from alternative child", global_state[0])
 
 
-
-
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
diff --git a/tests/test_logger.py b/tests/test_logger.py
index dbbdcf6c..81ecf8f0 100644
--- a/tests/test_logger.py
+++ b/tests/test_logger.py
@@ -1,4 +1,3 @@
-
 #
 # Aura Engine (https://gitlab.servus.at/aura/engine)
 #
@@ -25,12 +24,11 @@ from src.base.logger import AuraLogger
 from src.base.config import AuraConfig
 
 
-
-
 class TestLogger(unittest.TestCase):
     """
     Testing the Logger.
     """
+
     aura_logger = None
 
     def setUp(self):
@@ -41,7 +39,5 @@ class TestLogger(unittest.TestCase):
         self.assertTrue(self.aura_logger.logger.hasHandlers())
 
 
-
-
-if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+if __name__ == "__main__":
+    unittest.main()
-- 
GitLab