merge liquidsoap commands, different instances of liquidsoap for each station
This commit is contained in:
		@ -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:
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user