#137 Deployment: **Upgrade to Liquidsoap 2.4**: code has been adapted to work with liquidsoap 2.4 Co-authored-by: bkfox <thomas bkfox net> Reviewed-on: #138
This commit is contained in:
		@ -77,9 +77,12 @@ class Metadata:
 | 
			
		||||
            air_time = tz.datetime.strptime(air_time, "%Y/%m/%d %H:%M:%S")
 | 
			
		||||
            return local_tz.localize(air_time)
 | 
			
		||||
 | 
			
		||||
    def validate(self, data):
 | 
			
		||||
    def validate(self, data, as_dict=False):
 | 
			
		||||
        """Validate provided data and set as attribute (must already be
 | 
			
		||||
        declared)"""
 | 
			
		||||
        if as_dict and isinstance(data, list):
 | 
			
		||||
            data = {v[0]: v[1] for v in data}
 | 
			
		||||
 | 
			
		||||
        for key, value in data.items():
 | 
			
		||||
            if hasattr(self, key) and not callable(getattr(self, key)):
 | 
			
		||||
                setattr(self, key, value)
 | 
			
		||||
 | 
			
		||||
@ -133,8 +133,10 @@ class Monitor:
 | 
			
		||||
        # get sound
 | 
			
		||||
        diff = None
 | 
			
		||||
        sound = Sound.objects.path(air_uri).first()
 | 
			
		||||
        if sound and sound.episode_id is not None:
 | 
			
		||||
            diff = Diffusion.objects.episode(id=sound.episode_id).on_air().now(air_time).first()
 | 
			
		||||
        if sound:
 | 
			
		||||
            ids = sound.episodesound_set.values_list("episode_id", flat=True)
 | 
			
		||||
            if ids:
 | 
			
		||||
                diff = Diffusion.objects.filter(episode_id__in=ids).on_air().now(air_time).first()
 | 
			
		||||
 | 
			
		||||
        # log sound on air
 | 
			
		||||
        return self.log(
 | 
			
		||||
@ -198,7 +200,7 @@ class Monitor:
 | 
			
		||||
            Diffusion.objects.station(self.station)
 | 
			
		||||
            .on_air()
 | 
			
		||||
            .now(now)
 | 
			
		||||
            .filter(episode__sound__type=Sound.TYPE_ARCHIVE)
 | 
			
		||||
            .filter(episode__episodesound__broadcast=True)
 | 
			
		||||
            .first()
 | 
			
		||||
        )
 | 
			
		||||
        # Can't use delay: diffusion may start later than its assigned start.
 | 
			
		||||
@ -227,7 +229,7 @@ class Monitor:
 | 
			
		||||
        return log
 | 
			
		||||
 | 
			
		||||
    def start_diff(self, source, diff):
 | 
			
		||||
        playlist = Sound.objects.episode(id=diff.episode_id).playlist()
 | 
			
		||||
        playlist = diff.episode.episodesound_set.all().broadcast().playlist()
 | 
			
		||||
        source.push(*playlist)
 | 
			
		||||
        self.log(
 | 
			
		||||
            type=Log.TYPE_START,
 | 
			
		||||
 | 
			
		||||
@ -43,9 +43,9 @@ class Source(Metadata):
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            self.remaining = None
 | 
			
		||||
 | 
			
		||||
        data = self.controller.send(self.id, ".get", parse=True)
 | 
			
		||||
        data = self.controller.send(f"var.get {self.id}_meta", parse_json=True)
 | 
			
		||||
        if data:
 | 
			
		||||
            self.validate(data if data and isinstance(data, dict) else {})
 | 
			
		||||
            self.validate(data if data and isinstance(data, (dict, list)) else {}, as_dict=True)
 | 
			
		||||
 | 
			
		||||
    def skip(self):
 | 
			
		||||
        """Skip the current source sound."""
 | 
			
		||||
@ -80,7 +80,7 @@ class PlaylistSource(Source):
 | 
			
		||||
 | 
			
		||||
    def get_sound_queryset(self):
 | 
			
		||||
        """Get playlist's sounds queryset."""
 | 
			
		||||
        return self.program.sound_set.archive()
 | 
			
		||||
        return self.program.sound_set.broadcast()
 | 
			
		||||
 | 
			
		||||
    def get_playlist(self):
 | 
			
		||||
        """Get playlist from db."""
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,7 @@ import subprocess
 | 
			
		||||
import psutil
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
 | 
			
		||||
from aircox.conf import settings
 | 
			
		||||
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
from ..connector import Connector
 | 
			
		||||
from .sources import PlaylistSource, QueueSource
 | 
			
		||||
 | 
			
		||||
@ -46,8 +45,8 @@ class Streamer:
 | 
			
		||||
        self.outputs = self.station.port_set.active().output()
 | 
			
		||||
 | 
			
		||||
        self.id = self.station.slug.replace("-", "_")
 | 
			
		||||
        self.path = os.path.join(station.path, "station.liq")
 | 
			
		||||
        self.connector = connector or Connector(os.path.join(station.path, "station.sock"))
 | 
			
		||||
        self.path = settings.get_dir(station, "station.liq")
 | 
			
		||||
        self.connector = connector or Connector(settings.get_dir(station, "station.sock"))
 | 
			
		||||
        self.init_sources()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@ -98,7 +97,6 @@ class Streamer:
 | 
			
		||||
            {
 | 
			
		||||
                "station": self.station,
 | 
			
		||||
                "streamer": self,
 | 
			
		||||
                "settings": settings,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        data = re.sub("[\t ]+\n", "\n", data)
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ from aircox_streamer.controllers import Monitor, Streamer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# force using UTC
 | 
			
		||||
tz.activate(timezone.UTC)
 | 
			
		||||
tz.activate(timezone.utc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Command(BaseCommand):
 | 
			
		||||
 | 
			
		||||
@ -10,9 +10,9 @@ Base liquidsoap station configuration.
 | 
			
		||||
 | 
			
		||||
{% block functions %}
 | 
			
		||||
{# Seek function #}
 | 
			
		||||
def seek(source, t) =
 | 
			
		||||
def seek(s, t) =
 | 
			
		||||
  t = float_of_string(default=0.,t)
 | 
			
		||||
  ret = source.seek(source,t)
 | 
			
		||||
  ret = source.seek(s,t)
 | 
			
		||||
  log("seek #{ret} seconds.")
 | 
			
		||||
  "#{ret}"
 | 
			
		||||
end
 | 
			
		||||
@ -30,6 +30,17 @@ def to_stream(live, stream)
 | 
			
		||||
  add(normalize=false, [live,stream])
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
{# Skip command #}
 | 
			
		||||
def add_skip_command(s) =
 | 
			
		||||
    def skip(_) =
 | 
			
		||||
        source.skip(s)
 | 
			
		||||
        "Done!"
 | 
			
		||||
    end
 | 
			
		||||
    server.register(namespace="#{source.id(s)}",
 | 
			
		||||
        usage="skip",
 | 
			
		||||
        description="Skip the current song.",
 | 
			
		||||
        "skip",skip)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
{% comment %}
 | 
			
		||||
An interactive source is a source that:
 | 
			
		||||
@ -45,10 +56,14 @@ def interactive (id, s) =
 | 
			
		||||
    server.register(namespace=id,
 | 
			
		||||
                    description="Get source's track remaining time",
 | 
			
		||||
                    usage="remaining",
 | 
			
		||||
                    "remaining", fun (_) ->  begin json_of(source.remaining(s)) end)
 | 
			
		||||
                    "remaining", fun (_) ->  begin json.stringify(source.remaining(s)) end)
 | 
			
		||||
 | 
			
		||||
    s = store_metadata(id=id, size=1, s)
 | 
			
		||||
    add_skip_command(s)
 | 
			
		||||
 | 
			
		||||
    {# metadata: create an interactive variable as "{id}_meta" #}
 | 
			
		||||
    s_meta = interactive.string("#{id}_meta", "")
 | 
			
		||||
    s = source.on_metadata(s, fun(meta) -> s_meta.set(json.stringify(meta)))
 | 
			
		||||
 | 
			
		||||
    s
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -66,9 +81,6 @@ end
 | 
			
		||||
set("server.socket", true)
 | 
			
		||||
set("server.socket.path", "{{ streamer.socket_path }}")
 | 
			
		||||
set("log.file.path", "{{ station.path }}/liquidsoap.log")
 | 
			
		||||
{% for key, value in settings.AIRCOX_LIQUIDSOAP_SET.items %}
 | 
			
		||||
set("{{ key|safe }}", {{ value|safe }})
 | 
			
		||||
{% endfor %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block config_extras %}
 | 
			
		||||
 | 
			
		||||
@ -146,24 +146,28 @@ def episode(program):
 | 
			
		||||
def sound(program, episode):
 | 
			
		||||
    sound = models.Sound(
 | 
			
		||||
        program=program,
 | 
			
		||||
        episode=episode,
 | 
			
		||||
        name="sound",
 | 
			
		||||
        type=models.Sound.TYPE_ARCHIVE,
 | 
			
		||||
        position=0,
 | 
			
		||||
        broadcast=True,
 | 
			
		||||
        file="sound.mp3",
 | 
			
		||||
    )
 | 
			
		||||
    sound.save(check=False)
 | 
			
		||||
    sound.save(sync=False)
 | 
			
		||||
    return sound
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def episode_sound(episode, sound):
 | 
			
		||||
    obj = models.EpisodeSound(episode=episode, sound=sound, position=0, broadcast=sound.broadcast)
 | 
			
		||||
    obj.save()
 | 
			
		||||
    return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def sounds(program):
 | 
			
		||||
    items = [
 | 
			
		||||
        models.Sound(
 | 
			
		||||
            name=f"sound {i}",
 | 
			
		||||
            program=program,
 | 
			
		||||
            type=models.Sound.TYPE_ARCHIVE,
 | 
			
		||||
            position=i,
 | 
			
		||||
            broadcast=True,
 | 
			
		||||
            file=f"sound-{i}.mp3",
 | 
			
		||||
        )
 | 
			
		||||
        for i in range(0, 3)
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ def monitor(streamer):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def diffusion(program, episode, sound):
 | 
			
		||||
def diffusion(program, episode, episode_sound):
 | 
			
		||||
    return baker.make(
 | 
			
		||||
        models.Diffusion,
 | 
			
		||||
        program=program,
 | 
			
		||||
@ -33,10 +33,10 @@ def diffusion(program, episode, sound):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def source(monitor, streamer, sound, diffusion):
 | 
			
		||||
def source(monitor, streamer, episode_sound, diffusion):
 | 
			
		||||
    source = next(monitor.streamer.playlists)
 | 
			
		||||
    source.uri = sound.file.path
 | 
			
		||||
    source.episode_id = sound.episode_id
 | 
			
		||||
    source.uri = episode_sound.sound.file.path
 | 
			
		||||
    source.episode_id = episode_sound.episode_id
 | 
			
		||||
    source.air_time = diffusion.start + tz.timedelta(seconds=10)
 | 
			
		||||
    return source
 | 
			
		||||
 | 
			
		||||
@ -185,7 +185,7 @@ class TestMonitor:
 | 
			
		||||
        monitor.trace_tracks(log)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db(transaction=True)
 | 
			
		||||
    def test_handle_diffusions(self, monitor, streamer, diffusion, sound):
 | 
			
		||||
    def test_handle_diffusions(self, monitor, streamer, diffusion, episode_sound):
 | 
			
		||||
        interface(
 | 
			
		||||
            monitor,
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
@ -67,7 +67,7 @@ class TestPlaylistSource:
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_get_sound_queryset(self, playlist_source, sounds):
 | 
			
		||||
        query = playlist_source.get_sound_queryset()
 | 
			
		||||
        assert all(r.program_id == playlist_source.program.pk and r.type == r.TYPE_ARCHIVE for r in query)
 | 
			
		||||
        assert all(r.program_id == playlist_source.program.pk and r.broadcast for r in query)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_get_playlist(self, playlist_source, sounds):
 | 
			
		||||
 | 
			
		||||
@ -137,7 +137,7 @@ class QueueSourceViewSet(SourceViewSet):
 | 
			
		||||
    model = controllers.QueueSource
 | 
			
		||||
 | 
			
		||||
    def get_sound_queryset(self, request):
 | 
			
		||||
        return Sound.objects.station(request.station).archive()
 | 
			
		||||
        return Sound.objects.station(request.station).broadcast()
 | 
			
		||||
 | 
			
		||||
    @action(detail=True, methods=["POST"])
 | 
			
		||||
    def push(self, request, pk):
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user