import os from django.db import models from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from filer.fields.image import FilerImageField from .. import settings __all__ = ("Station", "StationQuerySet", "Port") class StationQuerySet(models.QuerySet): def default(self, station=None): """Return station model instance, using defaults or given one.""" if station is None: return self.order_by("-default", "pk").first() return self.filter(pk=station).first() def active(self): return self.filter(active=True) class Station(models.Model): """Represents a radio station, to which multiple programs are attached and that is used as the top object for everything. A Station holds controllers for the audio stream generation too. Theses are set up when needed (at the first access to these elements) then cached. """ name = models.CharField(_("name"), max_length=64) slug = models.SlugField(_("slug"), max_length=64, unique=True) # FIXME: remove - should be decided only by Streamer controller + settings path = models.CharField( _("path"), help_text=_("path to the working directory"), max_length=256, blank=True, ) default = models.BooleanField( _("default station"), default=False, help_text=_("use this station as the main one."), ) active = models.BooleanField( _("active"), default=True, help_text=_("whether this station is still active or not."), ) logo = FilerImageField( on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Logo"), ) hosts = models.TextField( _("website's urls"), max_length=512, null=True, blank=True, help_text=_("specify one url per line"), ) audio_streams = models.TextField( _("audio streams"), max_length=2048, null=True, blank=True, help_text=_( "Audio streams urls used by station's player. One url " "a line." ), ) default_cover = FilerImageField( on_delete=models.SET_NULL, verbose_name=_("Default pages' cover"), null=True, blank=True, related_name="+", ) objects = StationQuerySet.as_manager() @cached_property def streams(self): """Audio streams as list of urls.""" return self.audio_streams.split("\n") if self.audio_streams else [] def __str__(self): return self.name def save(self, make_sources=True, *args, **kwargs): if not self.path: self.path = os.path.join( settings.AIRCOX_CONTROLLERS_WORKING_DIR, self.slug.replace("-", "_"), ) if self.default: qs = Station.objects.filter(default=True) if self.pk is not None: qs = qs.exclude(pk=self.pk) qs.update(default=False) super().save(*args, **kwargs) class PortQuerySet(models.QuerySet): def active(self, value=True): """Active ports.""" return self.filter(active=value) def output(self): """Filter in output ports.""" return self.filter(direction=Port.DIRECTION_OUTPUT) def input(self): """Fitler in input ports.""" return self.filter(direction=Port.DIRECTION_INPUT) class Port(models.Model): """Represent an audio input/output for the audio stream generation. You might want to take a look to LiquidSoap's documentation for the options available for each kind of input/output. Some port types may be not available depending on the direction of the port. """ DIRECTION_INPUT = 0x00 DIRECTION_OUTPUT = 0x01 DIRECTION_CHOICES = ( (DIRECTION_INPUT, _("input")), (DIRECTION_OUTPUT, _("output")), ) TYPE_JACK = 0x00 TYPE_ALSA = 0x01 TYPE_PULSEAUDIO = 0x02 TYPE_ICECAST = 0x03 TYPE_HTTP = 0x04 TYPE_HTTPS = 0x05 TYPE_FILE = 0x06 TYPE_CHOICES = ( (TYPE_JACK, "jack"), (TYPE_ALSA, "alsa"), (TYPE_PULSEAUDIO, "pulseaudio"), (TYPE_ICECAST, "icecast"), (TYPE_HTTP, "http"), (TYPE_HTTPS, "https"), (TYPE_FILE, _("file")), ) station = models.ForeignKey( Station, models.CASCADE, verbose_name=_("station") ) direction = models.SmallIntegerField( _("direction"), choices=DIRECTION_CHOICES ) type = models.SmallIntegerField(_("type"), choices=TYPE_CHOICES) active = models.BooleanField( _("active"), default=True, help_text=_("this port is active") ) settings = models.TextField( _("port settings"), help_text=_( "list of comma separated params available; " "this is put in the output config file as raw code; " "plugin related" ), blank=True, null=True, ) objects = PortQuerySet.as_manager() def __str__(self): return "{direction}: {type} #{id}".format( direction=self.get_direction_display(), type=self.get_type_display(), id=self.pk or "", ) def is_valid_type(self): """Return True if the type is available for the given direction.""" if self.direction == self.DIRECTION_INPUT: return self.type not in (self.TYPE_ICECAST, self.TYPE_FILE) return self.type not in (self.TYPE_HTTP, self.TYPE_HTTPS) def save(self, *args, **kwargs): if not self.is_valid_type(): raise ValueError( "port type is not allowed with the given port direction" ) return super().save(*args, **kwargs)