merge liquidsoap commands, different instances of liquidsoap for each station

This commit is contained in:
bkfox 2015-12-12 17:11:04 +01:00
parent 202992521e
commit 012e2dd9d0
6 changed files with 141 additions and 151 deletions

View File

@ -1,15 +1,63 @@
""" """
Monitor Liquidsoap's sources, logs, and even print what's on air. Main tool to work with liquidsoap. We can:
- monitor Liquidsoap's sources and do logs, print what's on air.
- generate configuration files and playlists for a given station
""" """
import os
import time import time
import re
from argparse import RawTextHelpFormatter from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.template.loader import render_to_string
from django.utils import timezone as tz from django.utils import timezone as tz
import aircox.programs.models as models
import aircox.programs.settings as programs_settings
import aircox.liquidsoap.settings as settings import aircox.liquidsoap.settings as settings
import aircox.liquidsoap.utils as utils import aircox.liquidsoap.utils as utils
import aircox.programs.models as models
class StationConfig:
controller = None
def __init__ (self, station):
self.controller = utils.Controller(station, False)
def handle (self, options):
if options.get('config') or options.get('all'):
self.make_config()
if options.get('streams') or options.get('all'):
self.make_playlists()
def make_config (self):
context = {
'controller': self.controller,
'settings': settings,
}
data = render_to_string('aircox_liquidsoap/station.liq', context)
data = re.sub(r'\s*\\\n', r'#\\n#', data)
data = data.replace('\n', '')
data = re.sub(r'#\\n#', '\n', data)
with open(self.controller.config_path, 'w+') as file:
file.write(data)
def make_playlists (self):
for stream in self.controller.streams.values():
program = stream.program
sounds = models.Sound.objects.filter(
# good_quality = True,
type = models.Sound.Type['archive'],
path__startswith = os.path.join(
programs_settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
program.path
)
)
with open(stream.path, 'w+') as file:
file.write('\n'.join(sound.path for sound in sounds))
class Command (BaseCommand): class Command (BaseCommand):
@ -18,38 +66,69 @@ class Command (BaseCommand):
def add_arguments (self, parser): def add_arguments (self, parser):
parser.formatter_class=RawTextHelpFormatter parser.formatter_class=RawTextHelpFormatter
parser.add_argument(
group = parser.add_argument_group('monitor')
group.add_argument(
'-o', '--on_air', action='store_true', '-o', '--on_air', action='store_true',
help='Print what is on air' help='print what is on air'
) )
parser.add_argument( group.add_argument(
'-m', '--monitor', action='store_true', '-m', '--monitor', action='store_true',
help='Runs in monitor mode' help='run in monitor mode'
) )
parser.add_argument( group.add_argument(
'-d', '--delay', type=int, '-d', '--delay', type=int,
default=1000, default=1000,
help='Time to sleep in milliseconds before update on monitor' help='time to sleep in milliseconds before update on monitor'
)
group = parser.add_argument_group('configuration')
parser.add_argument(
'-s', '--station', type=int,
help='generate files for the given station (if not set, do it for'
' all available stations)'
)
parser.add_argument(
'-c', '--config', action='store_true',
help='generate liquidsoap config file'
)
parser.add_argument(
'-S', '--streams', action='store_true',
help='generate all stream playlists'
)
parser.add_argument(
'-a', '--all', action='store_true',
help='shortcut for -cS'
) )
def handle (self, *args, **options): def handle (self, *args, **options):
connector = utils.Connector() if options.get('station'):
self.monitor = utils.Monitor(connector) station = models.Station.objects.get(id = options.get('station'))
StationConfig(station).handle(options)
elif options.get('all') or options.get('config') or \
options.get('streams'):
for station in models.Station.objects.filter(active = True):
StationConfig(station).handle(options)
if options.get('on_air') or options.get('monitor'):
self.handle_monitor(options)
def handle_monitor (self, options):
self.monitor = utils.Monitor()
self.monitor.update() self.monitor.update()
if options.get('on_air'): if options.get('on_air'):
for id, controller in self.monitor.controller.items(): for id, controller in self.monitor.controller.items():
print(id, controller.on_air) print(id, controller.on_air)
return
if options.get('monitor'): if options.get('monitor'):
delay = options.get('delay') / 1000 delay = options.get('delay') / 1000
while True: while True:
for controller in self.monitor.controllers.values(): for controller in self.monitor.controllers.values():
try: controller.monitor()
controller.monitor()
except Exception, e:
print(e)
time.sleep(delay) time.sleep(delay)
return

View File

@ -1,119 +0,0 @@
"""
Generate configuration files and playlists for liquidsoap using settings, streams and
so on
"""
import os
import re
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError
from django.views.generic.base import View
from django.template.loader import render_to_string
import aircox.liquidsoap.settings as settings
import aircox.liquidsoap.utils as utils
import aircox.programs.settings as programs_settings
import aircox.programs.models as models
class Command (BaseCommand):
help= __doc__
output_dir = settings.AIRCOX_LIQUIDSOAP_MEDIA
def add_arguments (self, parser):
parser.formatter_class=RawTextHelpFormatter
parser.add_argument(
'output', metavar='PATH', type=str, nargs='?',
help='force output to file (- to stdout) for single actions; to a '
'given dir when using --all')
parser.add_argument(
'-c', '--config', action='store_true',
help='Generate liquidsoap config file'
)
parser.add_argument(
'-d', '--diffusion', action='store_true',
help='Generate the playlist for the current scheduled diffusion'
)
parser.add_argument(
'-s', '--stream', type=int,
help='Generate the playlist of a stream with the given id'
)
parser.add_argument(
'-S', '--streams', action='store_true',
help='Generate all stream playlists'
)
parser.add_argument(
'-a', '--all', action='store_true',
help='Generate all playlists (stream and scheduled diffusion) '
'and config file'
)
def handle (self, *args, **options):
output = options.get('output') or None
if options.get('config'):
data = self.get_config(output = output)
return
if options.get('stream'):
stream = options['stream']
if type(stream) is int:
stream = models.Stream.objects.get(id = stream,
program__active = True)
data = self.get_playlist(stream, output = output)
return
if options.get('all') or options.get('streams'):
if output:
if not os.path.isdir(output):
raise CommandError('given output is not a directory')
self.output_dir = output
if options.get('all'):
self.handle(config = True)
for stream in models.Stream.objects.filter(program__active = True):
self.handle(stream = stream)
self.output_dir = settings.AIRCOX_LIQUIDSOAP_MEDIA
return
raise CommandError('nothing to do')
def print (self, data, path, default):
if path and path == '-':
print(data)
return
if not path:
path = os.path.join(self.output_dir, default)
with open(path, 'w+') as file:
file.write(data)
def get_config (self, output = None):
context = {
'monitor': utils.Monitor(),
'settings': settings,
}
data = render_to_string('aircox_liquidsoap/config.liq', context)
data = re.sub(r'\s*\\\n', r'#\\n#', data)
data = data.replace('\n', '')
data = re.sub(r'#\\n#', '\n', data)
self.print(data, output, 'aircox.liq')
def get_playlist (self, stream = None, output = None):
program = stream.program
source = utils.Source(program = program)
sounds = models.Sound.objects.filter(
# good_quality = True,
type = models.Sound.Type['archive'],
path__startswith = os.path.join(
programs_settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
program.path
)
)
data = '\n'.join(sound.path for sound in sounds)
self.print(data, output, utils.Source(program = program).path)

View File

@ -5,8 +5,6 @@ def ensure (key, default):
globals()[key] = getattr(settings, key, default) globals()[key] = getattr(settings, key, default)
ensure('AIRCOX_LIQUIDSOAP_SOCKET', '/tmp/liquidsoap.sock')
# dict of values to set (do not forget to escape chars) # dict of values to set (do not forget to escape chars)
ensure('AIRCOX_LIQUIDSOAP_SET', { ensure('AIRCOX_LIQUIDSOAP_SET', {
'log.file.path': '"/tmp/liquidsoap.log"', 'log.file.path': '"/tmp/liquidsoap.log"',
@ -18,7 +16,8 @@ ensure('AIRCOX_LIQUIDSOAP_SECURITY_SOURCE', '/media/data/musique/creation/Mega C
# start the server on monitor if not present # start the server on monitor if not present
ensure('AIRCOX_LIQUIDSOAP_AUTOSTART', True) ensure('AIRCOX_LIQUIDSOAP_AUTOSTART', True)
# output directory for the generated files # output directory for the generated files and socket. Each station has a subdir
# with the station's slug as name.
ensure('AIRCOX_LIQUIDSOAP_MEDIA', '/tmp') ensure('AIRCOX_LIQUIDSOAP_MEDIA', '/tmp')

View File

@ -1,4 +1,7 @@
{# Utilities #} {# Context: #}
{# - controller: controller used to generate the current file #}
{# - settings: global settings #}
def interactive_source (id, s) = \ def interactive_source (id, s) = \
s = store_metadata(id=id, size=1, s) \ s = store_metadata(id=id, size=1, s) \
add_skip_command(s) \ add_skip_command(s) \
@ -13,13 +16,11 @@ end \
\ \
{# Config #} {# Config #}
set("server.socket", true) \ set("server.socket", true) \
set("server.socket.path", "{{ settings.AIRCOX_LIQUIDSOAP_SOCKET }}") \ set("server.socket.path", {{ controller.socket_path }}) \
{% for key, value in settings.AIRCOX_LIQUIDSOAP_SET.items %} {% for key, value in settings.AIRCOX_LIQUIDSOAP_SET.items %}
set("{{ key|safe }}", {{ value|safe }}) \ set("{{ key|safe }}", {{ value|safe }}) \
{% endfor %} {% endfor %}
\ \
\
{% for controller in monitor.controllers.values %}
{# station #} {# station #}
{{ controller.id }} = interactive_source ( \ {{ controller.id }} = interactive_source ( \
"{{ controller.id }}", \ "{{ controller.id }}", \
@ -72,5 +73,4 @@ output.{{ output.get_type_display }}( \
{% endif %} {% endif %}
) \ ) \
{% endfor %} {% endfor %}
{% endfor %}

View File

@ -22,7 +22,7 @@ class Connector:
""" """
__socket = None __socket = None
__available = False __available = False
address = settings.AIRCOX_LIQUIDSOAP_SOCKET address = None
@property @property
def available (self): def available (self):
@ -144,6 +144,7 @@ class Source:
""" """
return os.path.join( return os.path.join(
settings.AIRCOX_LIQUIDSOAP_MEDIA, settings.AIRCOX_LIQUIDSOAP_MEDIA,
self.station.slug,
self.id + '.m3u' self.id + '.m3u'
) )
@ -313,6 +314,9 @@ class Dealer (Source):
class Controller: class Controller:
"""
Main class controller for station and sources (streams and dealer)
"""
connector = None connector = None
station = None # the related station station = None # the related station
master = None # master source (station's source) master = None # master source (station's source)
@ -331,15 +335,45 @@ class Controller:
def name (self): def name (self):
return self.master and self.master.name return self.master and self.master.name
def __init__ (self, station, connector = None): @property
def path (self):
""" """
use_connector: avoids the creation of a Connector, in case it is not needed Directory path where all station's related files are put.
"""
return os.path.join(settings.AIRCOX_LIQUIDSOAP_MEDIA,
self.station.slug)
@property
def socket_path (self):
"""
Connector's socket path
"""
return os.path.join(self.path, 'station.sock')
@property
def config_path (self):
"""
Connector's socket path
"""
return os.path.join(self.path, 'station.liq')
def __init__ (self, station, connector = True):
"""
Params:
- station: managed station
- connector: if true, create a connector, else do not
Initialize a master, a dealer and all streams that are connected
to the given station; We ensure the existence of the controller's
files dir.
""" """
self.connector = connector
self.station = station self.station = station
self.station.controller = self self.station.controller = self
self.outputs = models.Output.objects.filter(station = station) self.outputs = models.Output.objects.filter(station = station)
os.makedirs(self.path, exist_ok = True)
self.connector = connector and Connector(self.socket_path)
self.master = Master(self) self.master = Master(self)
self.dealer = Dealer(self) self.dealer = Dealer(self)
self.streams = { self.streams = {
@ -419,11 +453,11 @@ class Monitor:
""" """
controllers = None controllers = None
def __init__ (self, connector = None): def __init__ (self):
self.controllers = { self.controllers = {
controller.id : controller controller.id : controller
for controller in [ for controller in [
Controller(station, connector) Controller(station, True)
for station in programs.Station.objects.filter(active = True) for station in programs.Station.objects.filter(active = True)
] ]
} }

View File

@ -14,11 +14,8 @@ view_monitor = None
def get_monitor(): def get_monitor():
global view_monitor global view_monitor
if not view_monitor: if not view_monitor:
view_monitor = utils.Monitor( view_monitor = utils.Monitor()
utils.Connector(address = settings.AIRCOX_LIQUIDSOAP_SOCKET)
)
return view_monitor return view_monitor
class Actions: class Actions: