### Description
The replay gain preferences are applied in the legacy code, but the
playout code was missing this feature. The replay gain was not applied
when playout fetched the schedules.
37d1a7685e/legacy/application/models/Schedule.php (L881-L886)
225 lines
6.7 KiB
Python
225 lines
6.7 KiB
Python
from datetime import datetime, time, timedelta
|
|
from operator import itemgetter
|
|
from typing import Dict
|
|
|
|
from libretime_api_client.v2 import ApiClient
|
|
from libretime_shared.datetime import time_in_milliseconds, time_in_seconds
|
|
|
|
from ..liquidsoap.models import StreamPreferences
|
|
from .events import (
|
|
ActionEvent,
|
|
AnyEvent,
|
|
EventKind,
|
|
Events,
|
|
FileEvent,
|
|
WebStreamEvent,
|
|
datetime_to_event_key,
|
|
event_isoparse,
|
|
parse_any_event,
|
|
)
|
|
|
|
|
|
def insert_event(events: Events, event_key: str, event: AnyEvent) -> None:
|
|
key = event_key
|
|
|
|
# Search for an empty slot
|
|
index = 0
|
|
while key in events:
|
|
# Ignore duplicate event
|
|
if event == events[key]:
|
|
return
|
|
|
|
key = f"{event_key}_{index}"
|
|
index += 1
|
|
|
|
events[key] = event
|
|
|
|
|
|
def get_schedule(api_client: ApiClient) -> Events:
|
|
stream_preferences = StreamPreferences(**api_client.get_stream_preferences().json())
|
|
|
|
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.list_schedule(
|
|
params={
|
|
"ends_after": f"{current_time_str}Z",
|
|
"ends_before": f"{end_time_str}Z",
|
|
"overbooked": False,
|
|
"position_status__gt": 0,
|
|
}
|
|
).json()
|
|
|
|
events: Dict[str, AnyEvent] = {}
|
|
for item in sorted(schedule, key=itemgetter("starts_at")):
|
|
item["starts_at"] = event_isoparse(item["starts_at"])
|
|
item["ends_at"] = event_isoparse(item["ends_at"])
|
|
|
|
show_instance = api_client.get_show_instance(item["instance"]).json()
|
|
show = api_client.get_show(show_instance["show"]).json()
|
|
|
|
if show["live_enabled"]:
|
|
show_instance["starts_at"] = event_isoparse(show_instance["starts_at"])
|
|
show_instance["ends_at"] = event_isoparse(show_instance["ends_at"])
|
|
generate_live_events(events, show_instance, stream_preferences)
|
|
|
|
if item["file"]:
|
|
file = api_client.get_file(item["file"]).json()
|
|
generate_file_events(events, item, file, show, stream_preferences)
|
|
|
|
elif item["stream"]:
|
|
webstream = api_client.get_webstream(item["stream"]).json()
|
|
generate_webstream_events(events, item, webstream, show)
|
|
|
|
return dict(sorted(events.items()))
|
|
|
|
|
|
def generate_live_events(
|
|
events: Events,
|
|
show_instance: dict,
|
|
stream_preferences: StreamPreferences,
|
|
):
|
|
transition = timedelta(seconds=stream_preferences.input_fade_transition)
|
|
|
|
switch_off = show_instance["ends_at"] - transition
|
|
kick_out = show_instance["ends_at"]
|
|
switch_off_event_key = datetime_to_event_key(switch_off)
|
|
kick_out_event_key = datetime_to_event_key(kick_out)
|
|
|
|
# If enabled, fade the input source out
|
|
if switch_off != kick_out:
|
|
switch_off_event = ActionEvent(
|
|
type=EventKind.ACTION,
|
|
event_type="switch_off",
|
|
start=switch_off,
|
|
end=switch_off,
|
|
)
|
|
insert_event(events, switch_off_event_key, switch_off_event)
|
|
|
|
# Then kick the source out
|
|
kick_out_event = ActionEvent(
|
|
type=EventKind.ACTION,
|
|
event_type="kick_out",
|
|
start=kick_out,
|
|
end=kick_out,
|
|
)
|
|
insert_event(events, kick_out_event_key, kick_out_event)
|
|
|
|
|
|
def generate_file_events(
|
|
events: Events,
|
|
schedule: dict,
|
|
file: dict,
|
|
show: dict,
|
|
stream_preferences: StreamPreferences,
|
|
):
|
|
"""
|
|
Generate events for a scheduled file.
|
|
"""
|
|
event = FileEvent(
|
|
type=EventKind.FILE,
|
|
row_id=schedule["id"],
|
|
start=schedule["starts_at"],
|
|
end=schedule["ends_at"],
|
|
uri=file["url"],
|
|
id=file["id"],
|
|
# Show data
|
|
show_name=show["name"],
|
|
# Extra data
|
|
fade_in=time_in_milliseconds(time.fromisoformat(schedule["fade_in"])),
|
|
fade_out=time_in_milliseconds(time.fromisoformat(schedule["fade_out"])),
|
|
cue_in=time_in_seconds(time.fromisoformat(schedule["cue_in"])),
|
|
cue_out=time_in_seconds(time.fromisoformat(schedule["cue_out"])),
|
|
# File data
|
|
track_title=file.get("track_title"),
|
|
artist_name=file.get("artist_name"),
|
|
mime=file["mime"],
|
|
replay_gain=file["replay_gain"],
|
|
filesize=file["size"],
|
|
)
|
|
|
|
if event.replay_gain is None:
|
|
event.replay_gain = 0.0
|
|
|
|
if stream_preferences.replay_gain_enabled:
|
|
event.replay_gain += stream_preferences.replay_gain_offset
|
|
else:
|
|
event.replay_gain = None
|
|
|
|
insert_event(events, event.start_key, event)
|
|
|
|
|
|
def generate_webstream_events(
|
|
events: Events,
|
|
schedule: dict,
|
|
webstream: dict,
|
|
show: dict,
|
|
):
|
|
"""
|
|
Generate events for a scheduled webstream.
|
|
"""
|
|
schedule_start_event_key = datetime_to_event_key(schedule["starts_at"])
|
|
schedule_end_event_key = datetime_to_event_key(schedule["ends_at"])
|
|
|
|
stream_buffer_start_event = WebStreamEvent(
|
|
type=EventKind.WEB_STREAM_BUFFER_START,
|
|
row_id=schedule["id"],
|
|
start=schedule["starts_at"] - timedelta(seconds=5),
|
|
end=schedule["starts_at"] - timedelta(seconds=5),
|
|
uri=webstream["url"],
|
|
id=webstream["id"],
|
|
# Show data
|
|
show_name=show["name"],
|
|
)
|
|
insert_event(events, schedule_start_event_key, stream_buffer_start_event)
|
|
|
|
stream_output_start_event = WebStreamEvent(
|
|
type=EventKind.WEB_STREAM_OUTPUT_START,
|
|
row_id=schedule["id"],
|
|
start=schedule["starts_at"],
|
|
end=schedule["ends_at"],
|
|
uri=webstream["url"],
|
|
id=webstream["id"],
|
|
# Show data
|
|
show_name=show["name"],
|
|
)
|
|
insert_event(events, schedule_start_event_key, stream_output_start_event)
|
|
|
|
# NOTE: stream_*_end were previously triggered 1 second before
|
|
# the schedule end.
|
|
stream_buffer_end_event = WebStreamEvent(
|
|
type=EventKind.WEB_STREAM_BUFFER_END,
|
|
row_id=schedule["id"],
|
|
start=schedule["ends_at"],
|
|
end=schedule["ends_at"],
|
|
uri=webstream["url"],
|
|
id=webstream["id"],
|
|
# Show data
|
|
show_name=show["name"],
|
|
)
|
|
insert_event(events, schedule_end_event_key, stream_buffer_end_event)
|
|
|
|
stream_output_end_event = WebStreamEvent(
|
|
type=EventKind.WEB_STREAM_OUTPUT_END,
|
|
row_id=schedule["id"],
|
|
start=schedule["ends_at"],
|
|
end=schedule["ends_at"],
|
|
uri=webstream["url"],
|
|
id=webstream["id"],
|
|
# Show data
|
|
show_name=show["name"],
|
|
)
|
|
insert_event(events, schedule_end_event_key, stream_output_end_event)
|
|
|
|
|
|
def receive_schedule(schedule: Dict[str, dict]) -> Events:
|
|
events: Dict[str, AnyEvent] = {}
|
|
|
|
for event_key, event in schedule.items():
|
|
events[event_key] = parse_any_event(event)
|
|
|
|
return events
|