fix some issues, make the liquidsoap monitor working

This commit is contained in:
bkfox 2015-11-23 02:04:37 +01:00
parent 25e3d4cb53
commit edfdd94eda
8 changed files with 88 additions and 123 deletions

View File

@ -1,10 +1,7 @@
"""
Control Liquidsoap
"""
import os
import re
import datetime
import collections
import time
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError
@ -14,74 +11,6 @@ import aircox_liquidsoap.settings as settings
import aircox_liquidsoap.utils as utils
import aircox_programs.models as models
class DiffusionInfo:
date = None
original = None
sounds = None
duration = 0
def __init__ (self, diffusion):
episode = diffusion.episode
self.original = diffusion
self.sounds = [ sound for sound in episode.sounds
if sound.type = models.Sound.Type['archive'] ]
self.sounds.sort(key = 'path')
self.date = diffusion.date
self.duration = episode.get_duration()
self.end = self.date + tz.datetime.timedelta(seconds = self.duration)
def __eq___ (self, info):
return self.original.id == info.original.id
class ControllerMonitor:
current = None
queue = None
def get_next (self, controller):
upcoming = models.Diffusion.get_next(
station = controller.station,
# diffusion__episode__not blank
# diffusion__episode__sounds not blank
)
return Monitor.Info(upcoming[0]) if upcoming else None
def playlist (self, controller):
dealer = controller.dealer
on_air = dealer.current_sound
playlist = dealer.playlist
next = self.queue[0]
# last track: time to reload playlist
if on_air == playlist[-1] or on_air not in playlist:
dealer.playlist = [sound.path for sound in next.sounds]
dealer.on = False
def current (self, controller):
# time to switch...
if on_air not in self.current.sounds:
self.current = self.queue.popleft()
if self.current.date <= tz.datetime.now() and not dealer.on:
dealer.on = True
print('start ', self.current.original)
# HERE
upcoming = self.get_next(controller)
if upcoming.date <= tz.datetime.now() and not self.current:
self.current = upcoming
if not self.upcoming or upcoming != self.upcoming:
dealer.playlist = [sound.path for sound in upcomming.sounds]
dealer.on = False
self.upcoming = upcoming
class Command (BaseCommand):
help= __doc__
@ -98,24 +27,27 @@ class Command (BaseCommand):
help='Runs in monitor mode'
)
parser.add_argument(
'-s', '--sleep', type=int,
default=1,
help='Time to sleep before update'
'-d', '--delay', type=int,
default=1000,
help='Time to sleep in milliseconds before update on monitor'
)
# start and run liquidsoap
def handle (self, *args, **options):
connector = utils.Connector()
self.monitor = utils.Monitor()
self.monitor = utils.Monitor(connector)
self.monitor.update()
if options.get('on_air'):
for id, controller in self.monitor.controller.items():
print(id, controller.master.current_sound())
if options.get('monitor'):
sleep =
delay = options.get('delay') / 1000
while True:
for controller in self.monitor.controllers.values():
controller.dealer.monitor()
time.sleep(delay)

View File

@ -1,11 +1,6 @@
{# Utilities #}
def interactive_source (id, s, ) = \
def apply_metadata(m) = \
m = json_of(compact=true, m) \
ignore(interactive.string('#{id}_meta', m)) \
end \
\
s = on_metadata(id = id, apply_metadata, s) \
s = store_metadata(id=id, size=1, s) \
add_skip_command(s) \
s \
end \

View File

@ -4,6 +4,7 @@ import re
import json
from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils import timezone as tz
from aircox_programs.utils import to_timedelta
import aircox_programs.models as models
@ -50,10 +51,10 @@ class Connector:
data = bytes(''.join([str(d) for d in data]) + '\n', encoding='utf-8')
try:
reg = re.compile('(.*)[\n\r]+END[\n\r]*$')
reg = re.compile(r'(.*)\s+END\s*$')
self.__socket.sendall(data)
data = ''
while not reg.match(data):
while not reg.search(data):
data += self.__socket.recv(1024).decode('unicode_escape')
if data:
@ -166,7 +167,7 @@ class Source:
@property
def current_sound (self):
self.update()
self.metadata['initial_uri']
self.metadata.get('initial_uri')
def stream_info (self):
"""
@ -201,15 +202,29 @@ class Source:
Return -1 in case no update happened
"""
if metadata:
if metadata is not None:
source = metadata.get('source') or ''
if self.program and not source.startswith(self.id):
return -1
self.metadata = metadata
return
r = self.connector.send('var.get ', self.id + '_meta', parse_json=True)
return self.update(metadata = r) if r else -1
# r = self.connector.send('var.get ', self.id + '_meta', parse_json=True)
r = self.connector.send(self.id, '.get', parse=True)
return self.update(metadata = r or {})
class Master (Source):
"""
A master Source
"""
def update (self, metadata = None):
if metadata is not None:
return super().update(metadata)
r = self.connector.send('request.on_air')
r = self.connector.send('request.metadata ', r, parse = True)
return self.update(metadata = r or {})
class Dealer (Source):
@ -221,7 +236,7 @@ class Dealer (Source):
@property
def id (self):
return self.station.name + '_dealer'
return self.station.slug + '_dealer'
def stream_info (self):
pass
@ -257,31 +272,24 @@ class Dealer (Source):
file.write('\n'.join(sounds))
def __get_queue (self, date):
def __get_next (self, date, on_air):
"""
Return a list of diffusion candidates of being running right now.
Add an attribute "sounds" with the episode's archives.
Return which diffusion should be played now and not playing
"""
r = [ models.Diffusion.get_prev(self.station, date),
models.Diffusion.get_next(self.station, date) ]
r = [ diffusion.prefetch_related('episode__sounds')[0]
r = [ diffusion.prefetch_related('sounds')[0]
for diffusion in r if diffusion.count() ]
for diffusion in r:
setattr(diffusion, 'sounds',
[ sound.path for sound in diffusion.get_sounds() ])
return r
def __what_now (self, date, on_air, queue):
"""
Return which diffusion is on_air from the given queue
"""
for diffusion in queue:
duration = diffusion.archives_duration()
end_at = diffusion.date + tz.timedelta(seconds = diffusion.archives_duration())
for diffusion in r:
duration = to_timedelta(diffusion.archives_duration())
end_at = diffusion.date + duration
if end_at < date:
continue
if diffusion.sounds and on_air in diffusion.sounds:
diffusion.playlist = [ sound.path
for sound in diffusion.get_archives() ]
if diffusion.playlist and on_air not in diffusion.playlist:
return diffusion
def monitor (self):
@ -289,12 +297,25 @@ class Dealer (Source):
Monitor playlist (if it is time to load) and if it time to trigger
the button to start a diffusion.
"""
on_air = self.current_soudn
playlist = self.playlist
on_air = self.current_sound
now = tz.make_aware(tz.datetime.now())
queue = self.__get_queue()
current_diffusion = self.__what_now()
diff = self.__get_next(now, on_air)
if not diff:
return # there is nothing we can do
# playlist reload
if self.playlist != diff.playlist:
if not playlist or on_air == playlist[-1] or \
on_air not in playlist:
self.on = False
self.playlist = diff.playlist
# run the diff
if self.playlist == diff.playlist and diff.date <= now:
# FIXME: log
self.on = True
class Controller:
@ -324,7 +345,7 @@ class Controller:
self.station = station
self.station.controller = self
self.master = Source(self)
self.master = Master(self)
self.dealer = Dealer(self)
self.streams = {
source.id : source

View File

@ -53,6 +53,7 @@ class SoundAdmin (NameableAdmin):
(None, { 'fields': ['embed', 'duration', 'mtime'] }),
(None, { 'fields': ['removed', 'good_quality', 'public' ] } )
]
readonly_fields = ('path', 'duration',)
@admin.register(Stream)
@ -82,7 +83,7 @@ class ProgramAdmin (NameableAdmin):
@admin.register(Diffusion)
class DiffusionAdmin (admin.ModelAdmin):
def archives (self, obj):
sounds = obj.get_archives()
sounds = [ str(s) for s in obj.get_archives()]
return ', '.join(sounds) if sounds else ''
list_display = ('id', 'type', 'date', 'archives', 'program', 'initial')

View File

@ -18,10 +18,12 @@ Where:
To check quality of files, call the command sound_quality_check using the
parameters given by the setting AIRCOX_SOUND_QUALITY.
parameters given by the setting AIRCOX_SOUND_QUALITY. This script requires
Sox (and soxi).
"""
import os
import re
import subprocess
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError
@ -53,13 +55,19 @@ class Command (BaseCommand):
' matching episode on sounds that have not been yet assigned'
)
def handle (self, *args, **options):
if options.get('scan'):
self.scan()
if options.get('quality_check'):
self.check_quality(check = (not options.get('scan')) )
def _get_duration (self, path):
p = subprocess.Popen(['soxi', '-D', path], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
if not err:
return utils.seconds_to_time(int(float(out)))
def get_sound_info (self, program, path):
"""
Parse file name to get info on the assumption it has the correct
@ -82,6 +90,7 @@ class Command (BaseCommand):
else:
r = r.groupdict()
r['duration'] = self._get_duration(path)
r['name'] = r['name'].replace('_', ' ').capitalize()
r['path'] = path
return r
@ -109,12 +118,18 @@ class Command (BaseCommand):
@staticmethod
def check_sounds (qs):
"""
Only check for the sound existence or update
"""
# check files
for sound in qs:
if sound.check_on_file():
sound.save(check = False)
def scan (self):
"""
For all programs, scan dirs
"""
print('scan files for all programs...')
programs = Program.objects.filter()
@ -149,7 +164,8 @@ class Command (BaseCommand):
sound_info = self.get_sound_info(program, path)
sound = Sound.objects.get_or_create(
path = path,
defaults = { 'name': sound_info['name'] }
defaults = { 'name': sound_info['name'],
'duration': sound_info['duration'] or None }
)[0]
sound.__dict__.update(sound_kwargs)
sound.save(check = False)

View File

@ -49,8 +49,7 @@ class Stats:
args.append('stats')
p = subprocess.Popen(args,
stdout=subprocess.PIPE,
p = subprocess.Popen(args, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# sox outputs to stderr (my god WHYYYY)
out_, out = p.communicate()

View File

@ -564,16 +564,17 @@ class Diffusion (models.Model):
Get total duration of the archives. May differ from the schedule
duration.
"""
return sum([ sound.duration for sound in self.sounds
if sound.type == Sound.Type['archive']])
r = [ sound.duration
for sound in self.sounds.filter(type = Sound.Type['archive'])
if sound.duration ]
return sum(r) or self.duration
def get_archives (self):
"""
Return an ordered list of archives sounds for the given episode.
"""
r = [ sound for sound in self.sounds.all()
r = [ sound for sound in self.sounds.all().order_by('path')
if sound.type == Sound.Type['archive'] ]
r.sort(key = 'path')
return r
@classmethod

View File

@ -8,7 +8,7 @@ def to_timedelta (time):
return datetime.timedelta(
hours = time.hour,
minutes = time.minute,
seconds = time.seconds
seconds = time.second
)