merge liquidsoap commands, different instances of liquidsoap for each station
This commit is contained in:
parent
202992521e
commit
012e2dd9d0
|
@ -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 re
|
||||
from argparse import RawTextHelpFormatter
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.template.loader import render_to_string
|
||||
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.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):
|
||||
|
@ -18,38 +66,69 @@ class Command (BaseCommand):
|
|||
|
||||
def add_arguments (self, parser):
|
||||
parser.formatter_class=RawTextHelpFormatter
|
||||
parser.add_argument(
|
||||
|
||||
group = parser.add_argument_group('monitor')
|
||||
group.add_argument(
|
||||
'-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',
|
||||
help='Runs in monitor mode'
|
||||
help='run in monitor mode'
|
||||
)
|
||||
parser.add_argument(
|
||||
group.add_argument(
|
||||
'-d', '--delay', type=int,
|
||||
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):
|
||||
connector = utils.Connector()
|
||||
self.monitor = utils.Monitor(connector)
|
||||
if options.get('station'):
|
||||
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()
|
||||
|
||||
if options.get('on_air'):
|
||||
for id, controller in self.monitor.controller.items():
|
||||
print(id, controller.on_air)
|
||||
return
|
||||
|
||||
if options.get('monitor'):
|
||||
delay = options.get('delay') / 1000
|
||||
while True:
|
||||
for controller in self.monitor.controllers.values():
|
||||
try:
|
||||
controller.monitor()
|
||||
except Exception, e:
|
||||
print(e)
|
||||
controller.monitor()
|
||||
time.sleep(delay)
|
||||
return
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
@ -5,8 +5,6 @@ def ensure (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)
|
||||
ensure('AIRCOX_LIQUIDSOAP_SET', {
|
||||
'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
|
||||
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')
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{# Utilities #}
|
||||
{# Context: #}
|
||||
{# - controller: controller used to generate the current file #}
|
||||
{# - settings: global settings #}
|
||||
|
||||
def interactive_source (id, s) = \
|
||||
s = store_metadata(id=id, size=1, s) \
|
||||
add_skip_command(s) \
|
||||
|
@ -13,13 +16,11 @@ end \
|
|||
\
|
||||
{# Config #}
|
||||
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 %}
|
||||
set("{{ key|safe }}", {{ value|safe }}) \
|
||||
{% endfor %}
|
||||
\
|
||||
\
|
||||
{% for controller in monitor.controllers.values %}
|
||||
{# station #}
|
||||
{{ controller.id }} = interactive_source ( \
|
||||
"{{ controller.id }}", \
|
||||
|
@ -72,5 +73,4 @@ output.{{ output.get_type_display }}( \
|
|||
{% endif %}
|
||||
) \
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class Connector:
|
|||
"""
|
||||
__socket = None
|
||||
__available = False
|
||||
address = settings.AIRCOX_LIQUIDSOAP_SOCKET
|
||||
address = None
|
||||
|
||||
@property
|
||||
def available (self):
|
||||
|
@ -144,6 +144,7 @@ class Source:
|
|||
"""
|
||||
return os.path.join(
|
||||
settings.AIRCOX_LIQUIDSOAP_MEDIA,
|
||||
self.station.slug,
|
||||
self.id + '.m3u'
|
||||
)
|
||||
|
||||
|
@ -313,6 +314,9 @@ class Dealer (Source):
|
|||
|
||||
|
||||
class Controller:
|
||||
"""
|
||||
Main class controller for station and sources (streams and dealer)
|
||||
"""
|
||||
connector = None
|
||||
station = None # the related station
|
||||
master = None # master source (station's source)
|
||||
|
@ -331,15 +335,45 @@ class Controller:
|
|||
def name (self):
|
||||
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.controller = self
|
||||
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.dealer = Dealer(self)
|
||||
self.streams = {
|
||||
|
@ -419,11 +453,11 @@ class Monitor:
|
|||
"""
|
||||
controllers = None
|
||||
|
||||
def __init__ (self, connector = None):
|
||||
def __init__ (self):
|
||||
self.controllers = {
|
||||
controller.id : controller
|
||||
for controller in [
|
||||
Controller(station, connector)
|
||||
Controller(station, True)
|
||||
for station in programs.Station.objects.filter(active = True)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -14,11 +14,8 @@ view_monitor = None
|
|||
|
||||
def get_monitor():
|
||||
global view_monitor
|
||||
|
||||
if not view_monitor:
|
||||
view_monitor = utils.Monitor(
|
||||
utils.Connector(address = settings.AIRCOX_LIQUIDSOAP_SOCKET)
|
||||
)
|
||||
view_monitor = utils.Monitor()
|
||||
return view_monitor
|
||||
|
||||
class Actions:
|
||||
|
|
Loading…
Reference in New Issue
Block a user