From 161f2ccdcb1a77a5d4878e9a7663a60167defd01 Mon Sep 17 00:00:00 2001
From: jo <ljonas@riseup.net>
Date: Thu, 23 Mar 2023 02:11:02 +0100
Subject: [PATCH] refactor(playout): merge liquidsoap modules

---
 .../libretime_playout/player/liquidsoap.py    | 115 ++++++++++++++++-
 .../player/liquidsoap_gateway.py              | 121 ------------------
 .../tests/player/liquidsoap_gateway_test.py   |  45 -------
 playout/tests/player/liquidsoap_test.py       |  49 ++++++-
 4 files changed, 161 insertions(+), 169 deletions(-)
 delete mode 100644 playout/libretime_playout/player/liquidsoap_gateway.py
 delete mode 100644 playout/tests/player/liquidsoap_gateway_test.py

diff --git a/playout/libretime_playout/player/liquidsoap.py b/playout/libretime_playout/player/liquidsoap.py
index aa073bf9f..c614f4f4e 100644
--- a/playout/libretime_playout/player/liquidsoap.py
+++ b/playout/libretime_playout/player/liquidsoap.py
@@ -6,11 +6,124 @@ from typing import Dict, List, Optional, Set
 from ..liquidsoap.client import LiquidsoapClient
 from ..utils import seconds_between
 from .events import ActionEvent, AnyEvent, EventKind, FileEvent, WebStreamEvent
-from .liquidsoap_gateway import TelnetLiquidsoap
 
 logger = logging.getLogger(__name__)
 
 
+def create_liquidsoap_annotation(file_event: FileEvent) -> str:
+    # We need liq_start_next value in the annotate. That is the value that controls
+    # overlap duration of crossfade.
+    annotations = {
+        "media_id": file_event.id,
+        "schedule_table_id": file_event.row_id,
+        "liq_start_next": "0",
+        "liq_fade_in": file_event.fade_in / 1000,
+        "liq_fade_out": file_event.fade_out / 1000,
+        "liq_cue_in": file_event.cue_in,
+        "liq_cue_out": file_event.cue_out,
+    }
+
+    if file_event.replay_gain is not None:
+        annotations["replay_gain"] = f"{file_event.replay_gain} dB"
+
+    # Override the the artist/title that Liquidsoap extracts from a file's metadata with
+    # the metadata we get from Airtime. (You can modify metadata in Airtime's library,
+    # which doesn't get saved back to the file.)
+    if file_event.artist_name:
+        annotations["artist"] = file_event.artist_name.replace('"', '\\"')
+
+    if file_event.track_title:
+        annotations["title"] = file_event.track_title.replace('"', '\\"')
+
+    annotations_str = ",".join(f'{key}="{value}"' for key, value in annotations.items())
+
+    return "annotate:" + annotations_str + ":" + str(file_event.local_filepath)
+
+
+class TelnetLiquidsoap:
+    current_prebuffering_stream_id: Optional[int] = None
+
+    def __init__(
+        self,
+        liq_client: LiquidsoapClient,
+        queues: List[int],
+    ):
+        self.liq_client = liq_client
+        self.queues = queues
+
+    def queue_clear_all(self):
+        try:
+            self.liq_client.queues_remove(*self.queues)
+        except OSError as exception:
+            logger.exception(exception)
+
+    def queue_remove(self, queue_id: int):
+        try:
+            self.liq_client.queues_remove(queue_id)
+        except OSError as exception:
+            logger.exception(exception)
+
+    def queue_push(self, queue_id: int, file_event: FileEvent):
+        try:
+            annotation = create_liquidsoap_annotation(file_event)
+            self.liq_client.queue_push(queue_id, annotation, file_event.show_name)
+        except OSError as exception:
+            logger.exception(exception)
+
+    def stop_web_stream_buffer(self):
+        try:
+            self.liq_client.web_stream_stop_buffer()
+        except OSError as exception:
+            logger.exception(exception)
+
+    def stop_web_stream_output(self):
+        try:
+            self.liq_client.web_stream_stop()
+        except OSError as exception:
+            logger.exception(exception)
+
+    def start_web_stream(self):
+        try:
+            self.liq_client.web_stream_start()
+            self.current_prebuffering_stream_id = None
+        except OSError as exception:
+            logger.exception(exception)
+
+    def start_web_stream_buffer(self, event: WebStreamEvent):
+        try:
+            self.liq_client.web_stream_start_buffer(event.row_id, event.uri)
+            self.current_prebuffering_stream_id = event.row_id
+        except OSError as exception:
+            logger.exception(exception)
+
+    def get_current_stream_id(self) -> str:
+        try:
+            return self.liq_client.web_stream_get_id()
+        except OSError as exception:
+            logger.exception(exception)
+            return "-1"
+
+    def disconnect_source(self, sourcename):
+        if sourcename not in ("master_dj", "live_dj"):
+            raise ValueError(f"invalid source name: {sourcename}")
+
+        try:
+            logger.debug("Disconnecting source: %s", sourcename)
+            self.liq_client.source_disconnect(sourcename)
+        except OSError as exception:
+            logger.exception(exception)
+
+    def switch_source(self, sourcename, status):
+        if sourcename not in ("master_dj", "live_dj", "scheduled_play"):
+            raise ValueError(f"invalid source name: {sourcename}")
+
+        try:
+            logger.debug('Switching source: %s to "%s" status', sourcename, status)
+            self.liq_client.source_switch_status(sourcename, status == "on")
+        except OSError as exception:
+            logger.exception(exception)
+
+
 class PypoLiquidsoap:
     def __init__(self, liq_client: LiquidsoapClient):
         self.liq_queue_tracker: Dict[int, Optional[FileEvent]] = {
diff --git a/playout/libretime_playout/player/liquidsoap_gateway.py b/playout/libretime_playout/player/liquidsoap_gateway.py
deleted file mode 100644
index 52d09e09c..000000000
--- a/playout/libretime_playout/player/liquidsoap_gateway.py
+++ /dev/null
@@ -1,121 +0,0 @@
-import logging
-from typing import List, Optional
-
-from ..liquidsoap.client import LiquidsoapClient
-from .events import FileEvent, WebStreamEvent
-
-logger = logging.getLogger(__name__)
-
-
-def create_liquidsoap_annotation(file_event: FileEvent) -> str:
-    # We need liq_start_next value in the annotate. That is the value that controls
-    # overlap duration of crossfade.
-    annotations = {
-        "media_id": file_event.id,
-        "schedule_table_id": file_event.row_id,
-        "liq_start_next": "0",
-        "liq_fade_in": file_event.fade_in / 1000,
-        "liq_fade_out": file_event.fade_out / 1000,
-        "liq_cue_in": file_event.cue_in,
-        "liq_cue_out": file_event.cue_out,
-    }
-
-    if file_event.replay_gain is not None:
-        annotations["replay_gain"] = f"{file_event.replay_gain} dB"
-
-    # Override the the artist/title that Liquidsoap extracts from a file's metadata with
-    # the metadata we get from Airtime. (You can modify metadata in Airtime's library,
-    # which doesn't get saved back to the file.)
-    if file_event.artist_name:
-        annotations["artist"] = file_event.artist_name.replace('"', '\\"')
-
-    if file_event.track_title:
-        annotations["title"] = file_event.track_title.replace('"', '\\"')
-
-    annotations_str = ",".join(f'{key}="{value}"' for key, value in annotations.items())
-
-    return "annotate:" + annotations_str + ":" + str(file_event.local_filepath)
-
-
-class TelnetLiquidsoap:
-    current_prebuffering_stream_id: Optional[int] = None
-
-    def __init__(
-        self,
-        liq_client: LiquidsoapClient,
-        queues: List[int],
-    ):
-        self.liq_client = liq_client
-        self.queues = queues
-
-    def queue_clear_all(self):
-        try:
-            self.liq_client.queues_remove(*self.queues)
-        except OSError as exception:
-            logger.exception(exception)
-
-    def queue_remove(self, queue_id: int):
-        try:
-            self.liq_client.queues_remove(queue_id)
-        except OSError as exception:
-            logger.exception(exception)
-
-    def queue_push(self, queue_id: int, file_event: FileEvent):
-        try:
-            annotation = create_liquidsoap_annotation(file_event)
-            self.liq_client.queue_push(queue_id, annotation, file_event.show_name)
-        except OSError as exception:
-            logger.exception(exception)
-
-    def stop_web_stream_buffer(self):
-        try:
-            self.liq_client.web_stream_stop_buffer()
-        except OSError as exception:
-            logger.exception(exception)
-
-    def stop_web_stream_output(self):
-        try:
-            self.liq_client.web_stream_stop()
-        except OSError as exception:
-            logger.exception(exception)
-
-    def start_web_stream(self):
-        try:
-            self.liq_client.web_stream_start()
-            self.current_prebuffering_stream_id = None
-        except OSError as exception:
-            logger.exception(exception)
-
-    def start_web_stream_buffer(self, event: WebStreamEvent):
-        try:
-            self.liq_client.web_stream_start_buffer(event.row_id, event.uri)
-            self.current_prebuffering_stream_id = event.row_id
-        except OSError as exception:
-            logger.exception(exception)
-
-    def get_current_stream_id(self) -> str:
-        try:
-            return self.liq_client.web_stream_get_id()
-        except OSError as exception:
-            logger.exception(exception)
-            return "-1"
-
-    def disconnect_source(self, sourcename):
-        if sourcename not in ("master_dj", "live_dj"):
-            raise ValueError(f"invalid source name: {sourcename}")
-
-        try:
-            logger.debug("Disconnecting source: %s", sourcename)
-            self.liq_client.source_disconnect(sourcename)
-        except OSError as exception:
-            logger.exception(exception)
-
-    def switch_source(self, sourcename, status):
-        if sourcename not in ("master_dj", "live_dj", "scheduled_play"):
-            raise ValueError(f"invalid source name: {sourcename}")
-
-        try:
-            logger.debug('Switching source: %s to "%s" status', sourcename, status)
-            self.liq_client.source_switch_status(sourcename, status == "on")
-        except OSError as exception:
-            logger.exception(exception)
diff --git a/playout/tests/player/liquidsoap_gateway_test.py b/playout/tests/player/liquidsoap_gateway_test.py
deleted file mode 100644
index ea5cf5843..000000000
--- a/playout/tests/player/liquidsoap_gateway_test.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from datetime import datetime
-from pathlib import Path
-from unittest import mock
-
-from dateutil.tz import tzutc
-
-from libretime_playout.player.events import EventKind, FileEvent
-from libretime_playout.player.liquidsoap_gateway import create_liquidsoap_annotation
-
-
-@mock.patch("libretime_playout.player.events.CACHE_DIR", Path("/fake"))
-def test_create_liquidsoap_annotation():
-    file_event = FileEvent(
-        type=EventKind.FILE,
-        row_id=1,
-        start=datetime(2022, 9, 5, 11, tzinfo=tzutc()),
-        end=datetime(2022, 9, 5, 11, 5, 2, tzinfo=tzutc()),
-        uri=None,
-        id=2,
-        show_name="Show 1",
-        fade_in=500.0,
-        fade_out=500.0,
-        cue_in=13.7008,
-        cue_out=315.845,
-        track_title='My Friend the "Forest"',
-        artist_name="Nils Frahm",
-        mime="audio/flac",
-        replay_gain=11.46,
-        filesize=10000,
-    )
-
-    assert create_liquidsoap_annotation(file_event) == (
-        "annotate:"
-        'media_id="2",'
-        'schedule_table_id="1",'
-        'liq_start_next="0",'
-        'liq_fade_in="0.5",'
-        'liq_fade_out="0.5",'
-        'liq_cue_in="13.7008",'
-        'liq_cue_out="315.845",'
-        'replay_gain="11.46 dB",'
-        'artist="Nils Frahm",'
-        'title="My Friend the \\"Forest\\""'
-        ":/fake/2.flac"
-    )
diff --git a/playout/tests/player/liquidsoap_test.py b/playout/tests/player/liquidsoap_test.py
index b1028ffd2..fe1aa10bf 100644
--- a/playout/tests/player/liquidsoap_test.py
+++ b/playout/tests/player/liquidsoap_test.py
@@ -1,6 +1,51 @@
-from unittest.mock import MagicMock
+from datetime import datetime
+from pathlib import Path
+from unittest.mock import MagicMock, patch
 
-from libretime_playout.player.liquidsoap import PypoLiquidsoap
+from dateutil.tz import tzutc
+
+from libretime_playout.player.events import EventKind, FileEvent
+from libretime_playout.player.liquidsoap import (
+    PypoLiquidsoap,
+    create_liquidsoap_annotation,
+)
+
+
+@patch("libretime_playout.player.events.CACHE_DIR", Path("/fake"))
+def test_create_liquidsoap_annotation():
+    file_event = FileEvent(
+        type=EventKind.FILE,
+        row_id=1,
+        start=datetime(2022, 9, 5, 11, tzinfo=tzutc()),
+        end=datetime(2022, 9, 5, 11, 5, 2, tzinfo=tzutc()),
+        uri=None,
+        id=2,
+        show_name="Show 1",
+        fade_in=500.0,
+        fade_out=500.0,
+        cue_in=13.7008,
+        cue_out=315.845,
+        track_title='My Friend the "Forest"',
+        artist_name="Nils Frahm",
+        mime="audio/flac",
+        replay_gain=11.46,
+        filesize=10000,
+    )
+
+    assert create_liquidsoap_annotation(file_event) == (
+        "annotate:"
+        'media_id="2",'
+        'schedule_table_id="1",'
+        'liq_start_next="0",'
+        'liq_fade_in="0.5",'
+        'liq_fade_out="0.5",'
+        'liq_cue_in="13.7008",'
+        'liq_cue_out="315.845",'
+        'replay_gain="11.46 dB",'
+        'artist="Nils Frahm",'
+        'title="My Friend the \\"Forest\\""'
+        ":/fake/2.flac"
+    )
 
 
 def test_liquidsoap():