From 1faf5a724bfee48679b59a035ca7161d51669459 Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 24 Jun 2022 16:42:46 +0200 Subject: [PATCH] chore: move api client get_schedule to playout --- .github/workflows/playout.yml | 1 - api-client/libretime_api_client/version2.py | 142 ----------- api-client/tests/version2_test.py | 250 +------------------- playout/Makefile | 6 +- playout/libretime_playout/pypofetch.py | 3 +- playout/libretime_playout/schedule.py | 145 ++++++++++++ playout/tests/schedule_test.py | 248 +++++++++++++++++++ 7 files changed, 407 insertions(+), 388 deletions(-) create mode 100644 playout/libretime_playout/schedule.py create mode 100644 playout/tests/schedule_test.py diff --git a/.github/workflows/playout.yml b/.github/workflows/playout.yml index 47578708f..d790c5b52 100644 --- a/.github/workflows/playout.yml +++ b/.github/workflows/playout.yml @@ -22,4 +22,3 @@ jobs: uses: ./.github/workflows/_python.yml with: context: playout - test: false diff --git a/api-client/libretime_api_client/version2.py b/api-client/libretime_api_client/version2.py index f9824bfc2..c7dd715cb 100644 --- a/api-client/libretime_api_client/version2.py +++ b/api-client/libretime_api_client/version2.py @@ -7,25 +7,11 @@ # schedule a playlist one minute from the current time. ############################################################################### import logging -from datetime import datetime, timedelta -from typing import Dict - -from dateutil.parser import isoparse -from libretime_shared.datetime import ( - fromisoformat, - time_in_milliseconds, - time_in_seconds, -) from ._config import Config from .utils import RequestProvider LIBRETIME_API_VERSION = "2.0" -EVENT_KEY_FORMAT = "%Y-%m-%d-%H-%M-%S" - - -def datetime_to_event_key(value: datetime) -> str: - return value.strftime(EVENT_KEY_FORMAT) api_endpoints = {} @@ -55,135 +41,7 @@ class AirtimeApiClient: endpoints=api_endpoints, ) - def get_schedule(self): - current_time = datetime.utcnow() - end_time = current_time + timedelta(days=1) - - current_time_str = current_time.isoformat(timespec="seconds") - end_time_str = end_time.isoformat(timespec="seconds") - - schedule = self.services.schedule_url( - params={ - "ends__range": (f"{current_time_str}Z,{end_time_str}Z"), - "is_valid": True, - "playout_status__gt": 0, - } - ) - - events = {} - for item in schedule: - item["starts"] = isoparse(item["starts"]) - item["ends"] = isoparse(item["ends"]) - - show_instance = self.services.show_instance_url(id=item["instance_id"]) - show = self.services.show_url(id=show_instance["show_id"]) - - if item["file"]: - file = self.services.file_url(id=item["file_id"]) - events.update(generate_file_events(item, file, show)) - - elif item["stream"]: - webstream = self.services.webstream_url(id=item["stream_id"]) - events.update(generate_webstream_events(item, webstream, show)) - - return {"media": events} - def update_file(self, file_id, payload): data = self.services.file_url(id=file_id) data.update(payload) return self.services.file_url(id=file_id, _put_data=data) - - -def generate_file_events( - schedule: dict, - file: dict, - show: dict, -) -> Dict[str, dict]: - """ - Generate events for a scheduled file. - """ - events = {} - - schedule_start_event_key = datetime_to_event_key(schedule["starts"]) - schedule_end_event_key = datetime_to_event_key(schedule["ends"]) - - events[schedule_start_event_key] = { - "type": "file", - "independent_event": False, - "row_id": schedule["id"], - "start": schedule_start_event_key, - "end": schedule_end_event_key, - "uri": file["url"], - "id": file["id"], - # Show data - "show_name": show["name"], - # Extra data - "fade_in": time_in_milliseconds(fromisoformat(schedule["fade_in"])), - "fade_out": time_in_milliseconds(fromisoformat(schedule["fade_out"])), - "cue_in": time_in_seconds(fromisoformat(schedule["cue_in"])), - "cue_out": time_in_seconds(fromisoformat(schedule["cue_out"])), - "metadata": file, - "replay_gain": file["replay_gain"], - "filesize": file["filesize"], - } - - return events - - -def generate_webstream_events( - schedule: dict, - webstream: dict, - show: dict, -) -> Dict[str, dict]: - """ - Generate events for a scheduled webstream. - """ - events = {} - - schedule_start_event_key = datetime_to_event_key(schedule["starts"]) - schedule_end_event_key = datetime_to_event_key(schedule["ends"]) - - events[schedule_start_event_key] = { - "type": "stream_buffer_start", - "independent_event": True, - "row_id": schedule["id"], - "start": datetime_to_event_key(schedule["starts"] - timedelta(seconds=5)), - "end": datetime_to_event_key(schedule["starts"] - timedelta(seconds=5)), - "uri": webstream["url"], - "id": webstream["id"], - } - - events[f"{schedule_start_event_key}_0"] = { - "type": "stream_output_start", - "independent_event": True, - "row_id": schedule["id"], - "start": schedule_start_event_key, - "end": schedule_end_event_key, - "uri": webstream["url"], - "id": webstream["id"], - # Show data - "show_name": show["name"], - } - - # NOTE: stream_*_end were previously triggerered 1 second before the schedule end. - events[schedule_end_event_key] = { - "type": "stream_buffer_end", - "independent_event": True, - "row_id": schedule["id"], - "start": schedule_end_event_key, - "end": schedule_end_event_key, - "uri": webstream["url"], - "id": webstream["id"], - } - - events[f"{schedule_end_event_key}_0"] = { - "type": "stream_output_end", - "independent_event": True, - "row_id": schedule["id"], - "start": schedule_end_event_key, - "end": schedule_end_event_key, - "uri": webstream["url"], - "id": webstream["id"], - } - - return events diff --git a/api-client/tests/version2_test.py b/api-client/tests/version2_test.py index 128aaebe0..00d7010ca 100644 --- a/api-client/tests/version2_test.py +++ b/api-client/tests/version2_test.py @@ -18,246 +18,12 @@ general: return filepath -class MockRequestProvider: - @staticmethod - def schedule_url(_post_data=None, params=None, **kwargs): - return [ - { - "item_url": "http://192.168.10.100:8081/api/v2/schedule/17/", - "id": 17, - "starts": "2022-03-04T15:30:00Z", - "ends": "2022-03-04T15:33:50.674340Z", - "file": "http://192.168.10.100:8081/api/v2/files/1/", - "file_id": 1, - "stream": None, - "clip_length": "00:03:50.674340", - "fade_in": "00:00:00.500000", - "fade_out": "00:00:00.500000", - "cue_in": "00:00:01.310660", - "cue_out": "00:03:51.985000", - "media_item_played": False, - "instance": "http://192.168.10.100:8081/api/v2/show-instances/3/", - "instance_id": 3, - "playout_status": 1, - "broadcasted": 0, - "position": 0, - }, - { - "item_url": "http://192.168.10.100:8081/api/v2/schedule/18/", - "id": 18, - "starts": "2022-03-04T15:33:50.674340Z", - "ends": "2022-03-04T16:03:50.674340Z", - "file": None, - "stream": "http://192.168.10.100:8081/api/v2/webstreams/1/", - "stream_id": 1, - "clip_length": "00:30:00", - "fade_in": "00:00:00.500000", - "fade_out": "00:00:00.500000", - "cue_in": "00:00:00", - "cue_out": "00:30:00", - "media_item_played": False, - "instance": "http://192.168.10.100:8081/api/v2/show-instances/3/", - "instance_id": 3, - "playout_status": 1, - "broadcasted": 0, - "position": 1, - }, - ] - - @staticmethod - def show_instance_url(_post_data=None, params=None, **kwargs): - return { - "item_url": "http://192.168.10.100:8081/api/v2/show-instances/3/", - "id": 3, - "description": "", - "starts": "2022-03-04T15:30:00Z", - "ends": "2022-03-04T16:30:00Z", - "record": 0, - "rebroadcast": 0, - "time_filled": "00:33:50.674340", - "created": "2022-03-04T15:05:36Z", - "last_scheduled": "2022-03-04T15:05:46Z", - "modified_instance": False, - "autoplaylist_built": False, - "show": "http://192.168.10.100:8081/api/v2/shows/3/", - "show_id": 3, - "instance": None, - "file": None, - } - - @staticmethod - def show_url(_post_data=None, params=None, **kwargs): - return { - "item_url": "http://192.168.10.100:8081/api/v2/shows/3/", - "id": 3, - "name": "Test", - "url": "", - "genre": "", - "description": "", - "color": "", - "background_color": "", - "linked": False, - "is_linkable": True, - "image_path": "", - "has_autoplaylist": False, - "autoplaylist_repeat": False, - "autoplaylist": None, - } - - @staticmethod - def file_url(_post_data=None, params=None, **kwargs): - return { - "item_url": "http://192.168.10.100:8081/api/v2/files/1/", - "id": 1, - "name": "", - "mime": "audio/mp3", - "ftype": "audioclip", - "filepath": "imported/1/Bag Raiders/Bag Raiders/03 - Bag Raiders - Shooting Stars.mp3", - "import_status": 0, - "currently_accessing": 0, - "mtime": "2022-03-04T13:03:13Z", - "utime": "2022-03-04T13:03:11Z", - "lptime": "2022-03-04T14:26:18Z", - "md5": "65c497bdc702881be607c7961ae814fa", - "track_title": "Shooting Stars", - "artist_name": "Bag Raiders", - "bit_rate": 320000, - "sample_rate": 44100, - "format": None, - "length": "00:03:55.859578", - "album_title": "Bag Raiders", - "genre": "Electro", - "comments": None, - "year": "2010", - "track_number": 3, - "channels": 2, - "url": None, - "bpm": None, - "rating": None, - "encoded_by": None, - "disc_number": None, - "mood": None, - "label": None, - "composer": None, - "encoder": None, - "checksum": None, - "lyrics": None, - "orchestra": None, - "conductor": None, - "lyricist": None, - "original_lyricist": None, - "radio_station_name": None, - "info_url": None, - "artist_url": None, - "audio_source_url": None, - "radio_station_url": None, - "buy_this_url": None, - "isrc_number": None, - "catalog_number": None, - "original_artist": None, - "copyright": None, - "report_datetime": None, - "report_location": None, - "report_organization": None, - "subject": None, - "contributor": None, - "language": None, - "file_exists": True, - "replay_gain": "-8.77", - "cuein": "00:00:01.310660", - "cueout": "00:03:51.985000", - "silan_check": False, - "hidden": False, - "is_scheduled": True, - "is_playlist": False, - "filesize": 9505222, - "description": None, - "artwork": "imported/1/artwork/03 - Bag Raiders - Shooting Stars", - "track_type": "TEST", - "directory": "http://192.168.10.100:8081/api/v2/music-dirs/1/", - "edited_by": None, - "owner": "http://192.168.10.100:8081/api/v2/users/1/", - } - - @staticmethod - def webstream_url(_post_data=None, params=None, **kwargs): - return { - "item_url": "http://192.168.10.100:8081/api/v2/webstreams/1/", - "id": 1, - "name": "Test", - "description": "", - "url": "http://some-other-radio:8800/main.ogg", - "length": "00:30:00", - "creator_id": 1, - "mtime": "2022-03-04T13:11:20Z", - "utime": "2022-03-04T13:11:20Z", - "lptime": None, - "mime": "application/ogg", - } - - -def test_get_schedule(monkeypatch, config_filepath): +def test_api_client(config_filepath): client = AirtimeApiClient(config_path=config_filepath) - client.services = MockRequestProvider() - schedule = client.get_schedule() - assert schedule == { - "media": { - "2022-03-04-15-30-00": { - "type": "file", - "independent_event": False, - "row_id": 17, - "start": "2022-03-04-15-30-00", - "end": "2022-03-04-15-33-50", - # NOTE: The legacy schedule generator creates an url, - # but playout download the file using the file id, so - # we can safely ignore it here. - "uri": None, - "id": 1, - "show_name": "Test", - "fade_in": 500.0, - "fade_out": 500.0, - "cue_in": 1.31066, - "cue_out": 231.985, - "metadata": MockRequestProvider.file_url(), - "replay_gain": "-8.77", - "filesize": 9505222, - }, - "2022-03-04-15-33-50": { - "type": "stream_buffer_start", - "independent_event": True, - "row_id": 18, - "start": "2022-03-04-15-33-45", - "end": "2022-03-04-15-33-45", - "uri": "http://some-other-radio:8800/main.ogg", - "id": 1, - }, - "2022-03-04-15-33-50_0": { - "type": "stream_output_start", - "independent_event": True, - "row_id": 18, - "start": "2022-03-04-15-33-50", - "end": "2022-03-04-16-03-50", - "uri": "http://some-other-radio:8800/main.ogg", - "id": 1, - "show_name": "Test", - }, - "2022-03-04-16-03-50": { - "type": "stream_buffer_end", - "independent_event": True, - "row_id": 18, - "start": "2022-03-04-16-03-50", - "end": "2022-03-04-16-03-50", - "uri": "http://some-other-radio:8800/main.ogg", - "id": 1, - }, - "2022-03-04-16-03-50_0": { - "type": "stream_output_end", - "independent_event": True, - "row_id": 18, - "start": "2022-03-04-16-03-50", - "end": "2022-03-04-16-03-50", - "uri": "http://some-other-radio:8800/main.ogg", - "id": 1, - }, - } - } + assert callable(client.services.version_url) + assert callable(client.services.schedule_url) + assert callable(client.services.webstream_url) + assert callable(client.services.show_instance_url) + assert callable(client.services.show_url) + assert callable(client.services.file_url) + assert callable(client.services.file_download_url) diff --git a/playout/Makefile b/playout/Makefile index 681359164..5e430ae56 100644 --- a/playout/Makefile +++ b/playout/Makefile @@ -3,10 +3,12 @@ all: lint include ../tools/python.mk PIP_INSTALL := --editable .[dev] -PYLINT_ARG := libretime_liquidsoap libretime_playout || true -MYPY_ARG := libretime_liquidsoap libretime_playout || true +PYLINT_ARG := libretime_liquidsoap libretime_playout tests || true +MYPY_ARG := libretime_liquidsoap libretime_playout tests || true BANDIT_ARG := libretime_liquidsoap libretime_playout || true +PYTEST_ARG := --cov=libretime_liquidsoap --cov=libretime_playout tests format: .format lint: .format-check .pylint .mypy .bandit +test: .pytest clean: .clean diff --git a/playout/libretime_playout/pypofetch.py b/playout/libretime_playout/pypofetch.py index 5b1173f01..31075f95d 100644 --- a/playout/libretime_playout/pypofetch.py +++ b/playout/libretime_playout/pypofetch.py @@ -18,6 +18,7 @@ from loguru import logger from . import pure from .config import CACHE_DIR, POLL_INTERVAL, Config +from .schedule import get_schedule from .timeout import ls_timeout @@ -458,7 +459,7 @@ class PypoFetch(Thread): def manual_schedule_fetch(self): try: - self.schedule_data = self.api_client.get_schedule() + self.schedule_data = get_schedule(self.api_client) logger.debug(f"Received event from API client: {self.schedule_data}") self.process_schedule(self.schedule_data) return True diff --git a/playout/libretime_playout/schedule.py b/playout/libretime_playout/schedule.py new file mode 100644 index 000000000..34db28cd3 --- /dev/null +++ b/playout/libretime_playout/schedule.py @@ -0,0 +1,145 @@ +from datetime import datetime, timedelta +from typing import Dict + +from dateutil.parser import isoparse +from libretime_api_client.version2 import AirtimeApiClient as ApiClient +from libretime_shared.datetime import ( + fromisoformat, + time_in_milliseconds, + time_in_seconds, +) + +EVENT_KEY_FORMAT = "%Y-%m-%d-%H-%M-%S" + + +def datetime_to_event_key(value: datetime) -> str: + return value.strftime(EVENT_KEY_FORMAT) + + +def get_schedule(api_client: ApiClient): + current_time = datetime.utcnow() + end_time = current_time + timedelta(days=1) + + current_time_str = current_time.isoformat(timespec="seconds") + end_time_str = end_time.isoformat(timespec="seconds") + + schedule = api_client.services.schedule_url( + params={ + "ends__range": (f"{current_time_str}Z,{end_time_str}Z"), + "is_valid": True, + "playout_status__gt": 0, + } + ) + + events = {} + for item in schedule: + item["starts"] = isoparse(item["starts"]) + item["ends"] = isoparse(item["ends"]) + + show_instance = api_client.services.show_instance_url(id=item["instance_id"]) + show = api_client.services.show_url(id=show_instance["show_id"]) + + if item["file"]: + file = api_client.services.file_url(id=item["file_id"]) + events.update(generate_file_events(item, file, show)) + + elif item["stream"]: + webstream = api_client.services.webstream_url(id=item["stream_id"]) + events.update(generate_webstream_events(item, webstream, show)) + + return {"media": events} + + +def generate_file_events( + schedule: dict, + file: dict, + show: dict, +) -> Dict[str, dict]: + """ + Generate events for a scheduled file. + """ + events = {} + + schedule_start_event_key = datetime_to_event_key(schedule["starts"]) + schedule_end_event_key = datetime_to_event_key(schedule["ends"]) + + events[schedule_start_event_key] = { + "type": "file", + "independent_event": False, + "row_id": schedule["id"], + "start": schedule_start_event_key, + "end": schedule_end_event_key, + "uri": file["url"], + "id": file["id"], + # Show data + "show_name": show["name"], + # Extra data + "fade_in": time_in_milliseconds(fromisoformat(schedule["fade_in"])), + "fade_out": time_in_milliseconds(fromisoformat(schedule["fade_out"])), + "cue_in": time_in_seconds(fromisoformat(schedule["cue_in"])), + "cue_out": time_in_seconds(fromisoformat(schedule["cue_out"])), + "metadata": file, + "replay_gain": file["replay_gain"], + "filesize": file["filesize"], + } + + return events + + +def generate_webstream_events( + schedule: dict, + webstream: dict, + show: dict, +) -> Dict[str, dict]: + """ + Generate events for a scheduled webstream. + """ + events = {} + + schedule_start_event_key = datetime_to_event_key(schedule["starts"]) + schedule_end_event_key = datetime_to_event_key(schedule["ends"]) + + events[schedule_start_event_key] = { + "type": "stream_buffer_start", + "independent_event": True, + "row_id": schedule["id"], + "start": datetime_to_event_key(schedule["starts"] - timedelta(seconds=5)), + "end": datetime_to_event_key(schedule["starts"] - timedelta(seconds=5)), + "uri": webstream["url"], + "id": webstream["id"], + } + + events[f"{schedule_start_event_key}_0"] = { + "type": "stream_output_start", + "independent_event": True, + "row_id": schedule["id"], + "start": schedule_start_event_key, + "end": schedule_end_event_key, + "uri": webstream["url"], + "id": webstream["id"], + # Show data + "show_name": show["name"], + } + + # NOTE: stream_*_end were previously triggerered 1 second before the schedule end. + events[schedule_end_event_key] = { + "type": "stream_buffer_end", + "independent_event": True, + "row_id": schedule["id"], + "start": schedule_end_event_key, + "end": schedule_end_event_key, + "uri": webstream["url"], + "id": webstream["id"], + } + + events[f"{schedule_end_event_key}_0"] = { + "type": "stream_output_end", + "independent_event": True, + "row_id": schedule["id"], + "start": schedule_end_event_key, + "end": schedule_end_event_key, + "uri": webstream["url"], + "id": webstream["id"], + } + + return events diff --git a/playout/tests/schedule_test.py b/playout/tests/schedule_test.py new file mode 100644 index 000000000..6249fad4f --- /dev/null +++ b/playout/tests/schedule_test.py @@ -0,0 +1,248 @@ +from libretime_playout.schedule import get_schedule + + +class ApiClientServicesMock: + @staticmethod + def schedule_url(_post_data=None, params=None, **kwargs): + return [ + { + "item_url": "http://192.168.10.100:8081/api/v2/schedule/17/", + "id": 17, + "starts": "2022-03-04T15:30:00Z", + "ends": "2022-03-04T15:33:50.674340Z", + "file": "http://192.168.10.100:8081/api/v2/files/1/", + "file_id": 1, + "stream": None, + "clip_length": "00:03:50.674340", + "fade_in": "00:00:00.500000", + "fade_out": "00:00:00.500000", + "cue_in": "00:00:01.310660", + "cue_out": "00:03:51.985000", + "media_item_played": False, + "instance": "http://192.168.10.100:8081/api/v2/show-instances/3/", + "instance_id": 3, + "playout_status": 1, + "broadcasted": 0, + "position": 0, + }, + { + "item_url": "http://192.168.10.100:8081/api/v2/schedule/18/", + "id": 18, + "starts": "2022-03-04T15:33:50.674340Z", + "ends": "2022-03-04T16:03:50.674340Z", + "file": None, + "stream": "http://192.168.10.100:8081/api/v2/webstreams/1/", + "stream_id": 1, + "clip_length": "00:30:00", + "fade_in": "00:00:00.500000", + "fade_out": "00:00:00.500000", + "cue_in": "00:00:00", + "cue_out": "00:30:00", + "media_item_played": False, + "instance": "http://192.168.10.100:8081/api/v2/show-instances/3/", + "instance_id": 3, + "playout_status": 1, + "broadcasted": 0, + "position": 1, + }, + ] + + @staticmethod + def show_instance_url(_post_data=None, params=None, **kwargs): + return { + "item_url": "http://192.168.10.100:8081/api/v2/show-instances/3/", + "id": 3, + "description": "", + "starts": "2022-03-04T15:30:00Z", + "ends": "2022-03-04T16:30:00Z", + "record": 0, + "rebroadcast": 0, + "time_filled": "00:33:50.674340", + "created": "2022-03-04T15:05:36Z", + "last_scheduled": "2022-03-04T15:05:46Z", + "modified_instance": False, + "autoplaylist_built": False, + "show": "http://192.168.10.100:8081/api/v2/shows/3/", + "show_id": 3, + "instance": None, + "file": None, + } + + @staticmethod + def show_url(_post_data=None, params=None, **kwargs): + return { + "item_url": "http://192.168.10.100:8081/api/v2/shows/3/", + "id": 3, + "name": "Test", + "url": "", + "genre": "", + "description": "", + "color": "", + "background_color": "", + "linked": False, + "is_linkable": True, + "image_path": "", + "has_autoplaylist": False, + "autoplaylist_repeat": False, + "autoplaylist": None, + } + + @staticmethod + def file_url(_post_data=None, params=None, **kwargs): + return { + "item_url": "http://192.168.10.100:8081/api/v2/files/1/", + "id": 1, + "name": "", + "mime": "audio/mp3", + "ftype": "audioclip", + "filepath": "imported/1/Bag Raiders/Bag Raiders/03 - Bag Raiders - Shooting Stars.mp3", + "import_status": 0, + "currently_accessing": 0, + "mtime": "2022-03-04T13:03:13Z", + "utime": "2022-03-04T13:03:11Z", + "lptime": "2022-03-04T14:26:18Z", + "md5": "65c497bdc702881be607c7961ae814fa", + "track_title": "Shooting Stars", + "artist_name": "Bag Raiders", + "bit_rate": 320000, + "sample_rate": 44100, + "format": None, + "length": "00:03:55.859578", + "album_title": "Bag Raiders", + "genre": "Electro", + "comments": None, + "year": "2010", + "track_number": 3, + "channels": 2, + "url": None, + "bpm": None, + "rating": None, + "encoded_by": None, + "disc_number": None, + "mood": None, + "label": None, + "composer": None, + "encoder": None, + "checksum": None, + "lyrics": None, + "orchestra": None, + "conductor": None, + "lyricist": None, + "original_lyricist": None, + "radio_station_name": None, + "info_url": None, + "artist_url": None, + "audio_source_url": None, + "radio_station_url": None, + "buy_this_url": None, + "isrc_number": None, + "catalog_number": None, + "original_artist": None, + "copyright": None, + "report_datetime": None, + "report_location": None, + "report_organization": None, + "subject": None, + "contributor": None, + "language": None, + "file_exists": True, + "replay_gain": "-8.77", + "cuein": "00:00:01.310660", + "cueout": "00:03:51.985000", + "silan_check": False, + "hidden": False, + "is_scheduled": True, + "is_playlist": False, + "filesize": 9505222, + "description": None, + "artwork": "imported/1/artwork/03 - Bag Raiders - Shooting Stars", + "track_type": "TEST", + "directory": "http://192.168.10.100:8081/api/v2/music-dirs/1/", + "edited_by": None, + "owner": "http://192.168.10.100:8081/api/v2/users/1/", + } + + @staticmethod + def webstream_url(_post_data=None, params=None, **kwargs): + return { + "item_url": "http://192.168.10.100:8081/api/v2/webstreams/1/", + "id": 1, + "name": "Test", + "description": "", + "url": "http://some-other-radio:8800/main.ogg", + "length": "00:30:00", + "creator_id": 1, + "mtime": "2022-03-04T13:11:20Z", + "utime": "2022-03-04T13:11:20Z", + "lptime": None, + "mime": "application/ogg", + } + + +class ApiClientMock: + services = ApiClientServicesMock() + + +def test_get_schedule(): + api_client = ApiClientMock() + assert get_schedule(api_client) == { + "media": { + "2022-03-04-15-30-00": { + "type": "file", + "independent_event": False, + "row_id": 17, + "start": "2022-03-04-15-30-00", + "end": "2022-03-04-15-33-50", + # NOTE: The legacy schedule generator creates an url, + # but playout download the file using the file id, so + # we can safely ignore it here. + "uri": None, + "id": 1, + "show_name": "Test", + "fade_in": 500.0, + "fade_out": 500.0, + "cue_in": 1.31066, + "cue_out": 231.985, + "metadata": ApiClientServicesMock.file_url(), + "replay_gain": "-8.77", + "filesize": 9505222, + }, + "2022-03-04-15-33-50": { + "type": "stream_buffer_start", + "independent_event": True, + "row_id": 18, + "start": "2022-03-04-15-33-45", + "end": "2022-03-04-15-33-45", + "uri": "http://some-other-radio:8800/main.ogg", + "id": 1, + }, + "2022-03-04-15-33-50_0": { + "type": "stream_output_start", + "independent_event": True, + "row_id": 18, + "start": "2022-03-04-15-33-50", + "end": "2022-03-04-16-03-50", + "uri": "http://some-other-radio:8800/main.ogg", + "id": 1, + "show_name": "Test", + }, + "2022-03-04-16-03-50": { + "type": "stream_buffer_end", + "independent_event": True, + "row_id": 18, + "start": "2022-03-04-16-03-50", + "end": "2022-03-04-16-03-50", + "uri": "http://some-other-radio:8800/main.ogg", + "id": 1, + }, + "2022-03-04-16-03-50_0": { + "type": "stream_output_end", + "independent_event": True, + "row_id": 18, + "start": "2022-03-04-16-03-50", + "end": "2022-03-04-16-03-50", + "uri": "http://some-other-radio:8800/main.ogg", + "id": 1, + }, + } + }