forked from rc/aircox
rewrite streamer and controller -- much cleaner and efficient; continue to work on new architecture
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -10,20 +10,12 @@ from django.utils.functional import cached_property
|
||||
|
||||
|
||||
from aircox import settings, utils
|
||||
from .program import Program, BaseRerun, BaseRerunQuerySet
|
||||
from .program import Program, InProgramQuerySet, \
|
||||
BaseRerun, BaseRerunQuerySet
|
||||
from .page import Page, PageQuerySet
|
||||
|
||||
|
||||
__all__ = ['Episode', 'EpisodeQuerySet', 'Diffusion', 'DiffusionQuerySet']
|
||||
|
||||
|
||||
class EpisodeQuerySet(PageQuerySet):
|
||||
def station(self, station):
|
||||
return self.filter(program__station=station)
|
||||
|
||||
# FIXME: useful??? might use program.episode_set
|
||||
def program(self, program):
|
||||
return self.filter(program=program)
|
||||
__all__ = ['Episode', 'Diffusion', 'DiffusionQuerySet']
|
||||
|
||||
|
||||
class Episode(Page):
|
||||
@ -32,7 +24,7 @@ class Episode(Page):
|
||||
verbose_name=_('program'),
|
||||
)
|
||||
|
||||
objects = EpisodeQuerySet.as_manager()
|
||||
objects = InProgramQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Episode')
|
||||
@ -52,40 +44,31 @@ class Episode(Page):
|
||||
return cls(program=program, title=title)
|
||||
|
||||
class DiffusionQuerySet(BaseRerunQuerySet):
|
||||
def station(self, station):
|
||||
return self.filter(episode__program__station=station)
|
||||
|
||||
def program(self, program):
|
||||
return self.filter(program=program)
|
||||
def episode(self, episode=None, id=None):
|
||||
""" Diffusions for this episode """
|
||||
return self.filter(episode=episode) if id is None else \
|
||||
self.filter(episode__id=id)
|
||||
|
||||
def on_air(self):
|
||||
""" On air diffusions """
|
||||
return self.filter(type=Diffusion.Type.on_air)
|
||||
|
||||
def at(self, date=None):
|
||||
"""
|
||||
Return diffusions occuring at the given date, ordered by +start
|
||||
def now(self, now=None, order=True):
|
||||
""" Diffusions occuring now """
|
||||
now = now or tz.now()
|
||||
qs = self.filter(start__lte=now, end__gte=now).distinct()
|
||||
return qs.order_by('start') if order else qs
|
||||
|
||||
If date is a datetime instance, get diffusions that occurs at
|
||||
the given moment. If date is not a datetime object, it uses
|
||||
it as a date, and get diffusions that occurs this day.
|
||||
def today(self, today=None, order=True):
|
||||
""" Diffusions occuring today. """
|
||||
today = today or datetime.date.today()
|
||||
qs = self.filter(Q(start__date=today) | Q(end__date=today))
|
||||
return qs.order_by('start') if order else qs
|
||||
|
||||
When date is None, uses tz.now().
|
||||
"""
|
||||
# note: we work with localtime
|
||||
date = utils.date_or_default(date)
|
||||
|
||||
qs = self
|
||||
filters = None
|
||||
|
||||
if isinstance(date, datetime.datetime):
|
||||
# use datetime: we want diffusion that occurs around this
|
||||
# range
|
||||
filters = {'start__lte': date, 'end__gte': date}
|
||||
qs = qs.filter(**filters)
|
||||
else:
|
||||
# use date: we want diffusions that occurs this day
|
||||
qs = qs.filter(Q(start__date=date) | Q(end__date=date))
|
||||
return qs.order_by('start').distinct()
|
||||
def at(self, date, order=True):
|
||||
""" Return diffusions at specified date or datetime """
|
||||
return self.now(date, order) if isinstance(date, tz.datetime) else \
|
||||
self.today(date, order)
|
||||
|
||||
def after(self, date=None):
|
||||
"""
|
||||
@ -183,10 +166,12 @@ class Diffusion(BaseRerun):
|
||||
# self.check_conflicts()
|
||||
|
||||
def save_rerun(self):
|
||||
print('rerun save', self)
|
||||
self.episode = self.initial.episode
|
||||
self.program = self.episode.program
|
||||
|
||||
def save_original(self):
|
||||
def save_initial(self):
|
||||
print('initial save', self)
|
||||
self.program = self.episode.program
|
||||
if self.episode != self._initial['episode']:
|
||||
self.rerun_set.update(episode=self.episode, program=self.program)
|
||||
@ -221,20 +206,11 @@ class Diffusion(BaseRerun):
|
||||
|
||||
return tz.localtime(self.end, tz.get_current_timezone())
|
||||
|
||||
@property
|
||||
def original(self):
|
||||
""" Return the original diffusion (self or initial) """
|
||||
|
||||
return self.initial.original if self.initial else self
|
||||
|
||||
# TODO: property?
|
||||
def is_live(self):
|
||||
"""
|
||||
True if Diffusion is live (False if there are sounds files)
|
||||
"""
|
||||
|
||||
""" True if Diffusion is live (False if there are sounds files). """
|
||||
return self.type == self.Type.on_air and \
|
||||
not self.get_sounds(archive=True).count()
|
||||
not self.episode.sound_set.archive().count()
|
||||
|
||||
def get_playlist(self, **types):
|
||||
"""
|
||||
|
@ -21,8 +21,9 @@ __all__ = ['Log', 'LogQuerySet']
|
||||
|
||||
|
||||
class LogQuerySet(models.QuerySet):
|
||||
def station(self, station):
|
||||
return self.filter(station=station)
|
||||
def station(self, station=None, id=None):
|
||||
return self.filter(station=station) if id is None else \
|
||||
self.filter(station_id=id)
|
||||
|
||||
def at(self, date=None):
|
||||
date = utils.date_or_default(date)
|
||||
@ -189,16 +190,20 @@ class Log(models.Model):
|
||||
Other log
|
||||
"""
|
||||
|
||||
station = models.ForeignKey(
|
||||
Station, models.CASCADE,
|
||||
verbose_name=_('station'),
|
||||
help_text=_('related station'),
|
||||
)
|
||||
type = models.SmallIntegerField(
|
||||
choices=[(int(y), _(x.replace('_', ' ')))
|
||||
for x, y in Type.__members__.items()],
|
||||
blank=True, null=True,
|
||||
verbose_name=_('type'),
|
||||
)
|
||||
station = models.ForeignKey(
|
||||
Station, models.CASCADE,
|
||||
verbose_name=_('station'),
|
||||
help_text=_('related station'),
|
||||
date = models.DateTimeField(
|
||||
default=tz.now, db_index=True,
|
||||
verbose_name=_('date'),
|
||||
)
|
||||
source = models.CharField(
|
||||
# we use a CharField to avoid loosing logs information if the
|
||||
@ -207,10 +212,6 @@ class Log(models.Model):
|
||||
verbose_name=_('source'),
|
||||
help_text=_('identifier of the source related to this log'),
|
||||
)
|
||||
date = models.DateTimeField(
|
||||
default=tz.now, db_index=True,
|
||||
verbose_name=_('date'),
|
||||
)
|
||||
comment = models.CharField(
|
||||
max_length=512, blank=True, null=True,
|
||||
verbose_name=_('comment'),
|
||||
|
@ -66,7 +66,8 @@ class Program(Page):
|
||||
@property
|
||||
def path(self):
|
||||
""" Return program's directory path """
|
||||
return os.path.join(settings.AIRCOX_PROGRAMS_DIR, self.slug)
|
||||
return os.path.join(settings.AIRCOX_PROGRAMS_DIR,
|
||||
self.slug.replace('-', '_'))
|
||||
|
||||
@property
|
||||
def archives_path(self):
|
||||
@ -80,7 +81,6 @@ class Program(Page):
|
||||
|
||||
def __init__(self, *kargs, **kwargs):
|
||||
super().__init__(*kargs, **kwargs)
|
||||
|
||||
if self.slug:
|
||||
self.__initial_path = self.path
|
||||
|
||||
@ -137,7 +137,21 @@ class Program(Page):
|
||||
.update(path=Concat('path', Substr(F('path'), len(path_))))
|
||||
|
||||
|
||||
class BaseRerunQuerySet(models.QuerySet):
|
||||
class InProgramQuerySet(models.QuerySet):
|
||||
"""
|
||||
Queryset for model having a ForeignKey field "program" to `Program`.
|
||||
"""
|
||||
def station(self, station=None, id=None):
|
||||
return self.filter(program__station=station) if id is None else \
|
||||
self.filter(program__station__id=id)
|
||||
|
||||
def program(self, program=None, id=None):
|
||||
return self.filter(program=program) if id is None else \
|
||||
self.filter(program__id=id)
|
||||
|
||||
|
||||
class BaseRerunQuerySet(InProgramQuerySet):
|
||||
""" Queryset for BaseRerun (sub)classes. """
|
||||
def rerun(self):
|
||||
return self.filter(initial__isnull=False)
|
||||
|
||||
@ -147,8 +161,8 @@ class BaseRerunQuerySet(models.QuerySet):
|
||||
|
||||
class BaseRerun(models.Model):
|
||||
"""
|
||||
Abstract model offering rerun facilities.
|
||||
`start` datetime field or property must be implemented by sub-classes
|
||||
Abstract model offering rerun facilities. Assume `start` is a
|
||||
datetime field or attribute implemented by subclass.
|
||||
"""
|
||||
program = models.ForeignKey(
|
||||
Program, models.CASCADE,
|
||||
@ -157,10 +171,13 @@ class BaseRerun(models.Model):
|
||||
initial = models.ForeignKey(
|
||||
'self', models.SET_NULL, related_name='rerun_set',
|
||||
verbose_name=_('initial schedule'),
|
||||
limit_choices_to={'initial__isnull': True},
|
||||
blank=True, null=True,
|
||||
help_text=_('mark as rerun of this %(model_name)'),
|
||||
)
|
||||
|
||||
objects = BaseRerunQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
@ -22,15 +22,32 @@ __all__ = ['Sound', 'SoundQuerySet', 'Track']
|
||||
|
||||
|
||||
class SoundQuerySet(models.QuerySet):
|
||||
def episode(self, episode=None, id=None):
|
||||
return self.filter(episode=episode) if id is None else \
|
||||
self.filter(episode__id=id)
|
||||
|
||||
def diffusion(self, diffusion=None, id=None):
|
||||
return self.filter(episode__diffusion=diffusion) if id is None else \
|
||||
self.filter(episode__diffusion__id=id)
|
||||
|
||||
def podcasts(self):
|
||||
""" Return sound available as podcasts """
|
||||
""" Return sounds available as podcasts """
|
||||
return self.filter(Q(embed__isnull=False) | Q(is_public=True))
|
||||
|
||||
def episode(self, episode):
|
||||
return self.filter(episode=episode)
|
||||
def archive(self):
|
||||
""" Return sounds that are archives """
|
||||
return self.filter(type=Sound.Type.archive)
|
||||
|
||||
def diffusion(self, diffusion):
|
||||
return self.filter(episode__diffusion=diffusion)
|
||||
def paths(self, archive=True, order_by=True):
|
||||
"""
|
||||
Return paths as a flat list (exclude sound without path).
|
||||
If `order_by` is True, order by path.
|
||||
"""
|
||||
if archive:
|
||||
self = self.archive()
|
||||
if order_by:
|
||||
self = self.order_by('path')
|
||||
return self.filter(path__isnull=False).values_list('path', flat=True)
|
||||
|
||||
|
||||
class Sound(models.Model):
|
||||
@ -46,6 +63,7 @@ class Sound(models.Model):
|
||||
|
||||
name = models.CharField(_('name'), max_length=64)
|
||||
program = models.ForeignKey(
|
||||
# FIXME: not nullable?
|
||||
Program, models.SET_NULL, blank=True, null=True,
|
||||
verbose_name=_('program'),
|
||||
help_text=_('program related to it'),
|
||||
@ -95,6 +113,21 @@ class Sound(models.Model):
|
||||
|
||||
objects = SoundQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Sound')
|
||||
verbose_name_plural = _('Sounds')
|
||||
|
||||
def __str__(self):
|
||||
return '/'.join(self.path.split('/')[-3:])
|
||||
|
||||
def save(self, check=True, *args, **kwargs):
|
||||
if self.episode is not None and self.program is None:
|
||||
self.program = self.episode.program
|
||||
if check:
|
||||
self.check_on_file()
|
||||
self.__check_name()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_mtime(self):
|
||||
"""
|
||||
Get the last modification date from file
|
||||
@ -220,21 +253,6 @@ class Sound(models.Model):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.__check_name()
|
||||
|
||||
def save(self, check=True, *args, **kwargs):
|
||||
if self.episode is not None and self.program is None:
|
||||
self.program = self.episode.program
|
||||
if check:
|
||||
self.check_on_file()
|
||||
self.__check_name()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return '/'.join(self.path.split('/')[-3:])
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Sound')
|
||||
verbose_name_plural = _('Sounds')
|
||||
|
||||
|
||||
class Track(models.Model):
|
||||
"""
|
||||
|
@ -33,6 +33,7 @@ class Station(models.Model):
|
||||
"""
|
||||
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'),
|
||||
@ -47,70 +48,13 @@ class Station(models.Model):
|
||||
|
||||
objects = StationQuerySet.as_manager()
|
||||
|
||||
#
|
||||
# Controllers
|
||||
#
|
||||
__sources = None
|
||||
__dealer = None
|
||||
__streamer = None
|
||||
|
||||
def __prepare_controls(self):
|
||||
import aircox.controllers as controllers
|
||||
from .program import Program
|
||||
if not self.__streamer:
|
||||
self.__streamer = controllers.Streamer(station=self)
|
||||
self.__dealer = controllers.Source(station=self)
|
||||
self.__sources = [self.__dealer] + [
|
||||
controllers.Source(station=self, program=program)
|
||||
|
||||
for program in Program.objects.filter(stream__isnull=False)
|
||||
]
|
||||
|
||||
@property
|
||||
def inputs(self):
|
||||
"""
|
||||
Return all active input ports of the station
|
||||
"""
|
||||
return self.port_set.filter(
|
||||
direction=Port.Direction.input,
|
||||
active=True
|
||||
)
|
||||
|
||||
@property
|
||||
def outputs(self):
|
||||
""" Return all active output ports of the station """
|
||||
return self.port_set.filter(
|
||||
direction=Port.Direction.output,
|
||||
active=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def sources(self):
|
||||
""" Audio sources, dealer included """
|
||||
self.__prepare_controls()
|
||||
return self.__sources
|
||||
|
||||
@property
|
||||
def dealer(self):
|
||||
""" Get dealer control """
|
||||
self.__prepare_controls()
|
||||
return self.__dealer
|
||||
|
||||
@property
|
||||
def streamer(self):
|
||||
""" Audio controller for the station """
|
||||
self.__prepare_controls()
|
||||
return self.__streamer
|
||||
|
||||
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
|
||||
)
|
||||
self.path = os.path.join(settings.AIRCOX_CONTROLLERS_WORKING_DIR,
|
||||
self.slug.replace('-', '_'))
|
||||
|
||||
if self.default:
|
||||
qs = Station.objects.filter(default=True)
|
||||
|
Reference in New Issue
Block a user