move files
This commit is contained in:
		
							
								
								
									
										14
									
								
								liquidsoap/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								liquidsoap/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
# Aircox LiquidSoap
 | 
			
		||||
This application makes the bridge between Aircox and LiquidSoap. It can monitor scheduled and streamed programs and offer some controls on LiquidSoap.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## manage.py's commands
 | 
			
		||||
* ** liquidsoap **: monitor LiquidSoap, logs what is playing on the different sources, and plays scheduled diffusions;
 | 
			
		||||
* ** liquidsoap_files**: generates playlists and LiquidSoap config based on Programs' parameters;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
* Liquidsoap
 | 
			
		||||
* requirements.txt for python's dependencies
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										0
									
								
								liquidsoap/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								liquidsoap/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										8
									
								
								liquidsoap/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								liquidsoap/admin.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
import aircox.liquidsoap.models as models
 | 
			
		||||
 | 
			
		||||
@admin.register(models.Output)
 | 
			
		||||
class OutputAdmin (admin.ModelAdmin):
 | 
			
		||||
    list_display = ('id', 'type', 'station')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										150
									
								
								liquidsoap/management/commands/liquidsoap.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								liquidsoap/management/commands/liquidsoap.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,150 @@
 | 
			
		||||
"""
 | 
			
		||||
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.conf import settings as main_settings
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StationConfig:
 | 
			
		||||
    """
 | 
			
		||||
    Configuration and playlist generator for a station.
 | 
			
		||||
    """
 | 
			
		||||
    controller = None
 | 
			
		||||
 | 
			
		||||
    def __init__ (self, station):
 | 
			
		||||
        self.controller = utils.Controller(station, False)
 | 
			
		||||
 | 
			
		||||
    def handle (self, options):
 | 
			
		||||
        os.makedirs(self.controller.path, exist_ok = True)
 | 
			
		||||
        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):
 | 
			
		||||
        log_script = main_settings.BASE_DIR \
 | 
			
		||||
                     if hasattr(main_settings, 'BASE_DIR') else \
 | 
			
		||||
                     main_settings.PROJECT_ROOT
 | 
			
		||||
        log_script = os.path.join(log_script, 'manage.py') + \
 | 
			
		||||
                        ' liquidsoap_log'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        context = {
 | 
			
		||||
            'controller': self.controller,
 | 
			
		||||
            'settings': settings,
 | 
			
		||||
            'log_script': log_script,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
    help= __doc__
 | 
			
		||||
    output_dir = settings.AIRCOX_LIQUIDSOAP_MEDIA
 | 
			
		||||
 | 
			
		||||
    def add_arguments (self, parser):
 | 
			
		||||
        parser.formatter_class=RawTextHelpFormatter
 | 
			
		||||
 | 
			
		||||
        group = parser.add_argument_group('monitor')
 | 
			
		||||
        group.add_argument(
 | 
			
		||||
            '-o', '--on_air', action='store_true',
 | 
			
		||||
            help='print what is on air'
 | 
			
		||||
        )
 | 
			
		||||
        group.add_argument(
 | 
			
		||||
            '-m', '--monitor', action='store_true',
 | 
			
		||||
            help='run in monitor mode'
 | 
			
		||||
        )
 | 
			
		||||
        group.add_argument(
 | 
			
		||||
            '-d', '--delay', type=int,
 | 
			
		||||
            default=1000,
 | 
			
		||||
            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):
 | 
			
		||||
        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 as err:
 | 
			
		||||
                        print(err)
 | 
			
		||||
                time.sleep(delay)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										62
									
								
								liquidsoap/management/commands/liquidsoap_log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								liquidsoap/management/commands/liquidsoap_log.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
"""
 | 
			
		||||
This script is used by liquidsoap in order to log a file change. It should not
 | 
			
		||||
be used for other purposes.
 | 
			
		||||
"""
 | 
			
		||||
import os
 | 
			
		||||
from argparse import RawTextHelpFormatter
 | 
			
		||||
 | 
			
		||||
from django.utils import timezone as tz
 | 
			
		||||
from django.core.management.base import BaseCommand, CommandError
 | 
			
		||||
 | 
			
		||||
import aircox.programs.models as programs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Command (BaseCommand):
 | 
			
		||||
    help= __doc__
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def date(s):
 | 
			
		||||
        try:
 | 
			
		||||
            return tz.make_aware(tz.datetime.strptime(s, '%Y/%m/%d %H:%M:%S'))
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            raise argparse.ArgumentTypeError('Invalid date format')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def add_arguments (self, parser):
 | 
			
		||||
        parser.formatter_class=RawTextHelpFormatter
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '-c', '--comment', type=str,
 | 
			
		||||
            help='log comment'
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '-s', '--source', type=str,
 | 
			
		||||
            required=True,
 | 
			
		||||
            help='source path'
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '-p', '--path', type=str,
 | 
			
		||||
            required=True,
 | 
			
		||||
            help='sound path to log'
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '-d', '--date', type=Command.date,
 | 
			
		||||
            help='set date instead of now (using format "%Y/%m/%d %H:%M:%S")'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def handle (self, *args, **options):
 | 
			
		||||
        comment = options.get('comment') or ''
 | 
			
		||||
        path = os.path.realpath(options.get('path'))
 | 
			
		||||
 | 
			
		||||
        sound = programs.Sound.objects.filter(path = path)
 | 
			
		||||
        if sound:
 | 
			
		||||
            sound = sound[0]
 | 
			
		||||
        else:
 | 
			
		||||
            sound = None
 | 
			
		||||
            comment += '\nunregistered sound: {}'.format(path)
 | 
			
		||||
 | 
			
		||||
        programs.Log(source = options.get('source'),
 | 
			
		||||
                     comment = comment,
 | 
			
		||||
                     related_object = sound).save()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								liquidsoap/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								liquidsoap/models.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils.translation import ugettext as _, ugettext_lazy
 | 
			
		||||
 | 
			
		||||
import aircox.programs.models as programs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Output (models.Model):
 | 
			
		||||
    # Note: we don't translate the names since it is project names.
 | 
			
		||||
    Type = {
 | 
			
		||||
        'jack': 0x00,
 | 
			
		||||
        'alsa': 0x01,
 | 
			
		||||
        'icecast': 0x02,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    station = models.ForeignKey(
 | 
			
		||||
        programs.Station,
 | 
			
		||||
        verbose_name = _('station'),
 | 
			
		||||
    )
 | 
			
		||||
    type = models.SmallIntegerField(
 | 
			
		||||
        _('output type'),
 | 
			
		||||
        choices = [ (y, x) for x,y in Type.items() ],
 | 
			
		||||
        blank = True, null = True
 | 
			
		||||
    )
 | 
			
		||||
    settings = models.TextField(
 | 
			
		||||
        _('output settings'),
 | 
			
		||||
        help_text = _('list of comma separated params available; '
 | 
			
		||||
                      'this is put in the output config as raw code'),
 | 
			
		||||
        blank = True, null = True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								liquidsoap/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								liquidsoap/settings.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
def ensure (key, default):
 | 
			
		||||
    globals()[key] = getattr(settings, key, default)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# dict of values to set (do not forget to escape chars)
 | 
			
		||||
ensure('AIRCOX_LIQUIDSOAP_SET', {
 | 
			
		||||
    'log.file.path': '"/tmp/liquidsoap.log"',
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
# security source: used when no source are available
 | 
			
		||||
ensure('AIRCOX_LIQUIDSOAP_SECURITY_SOURCE', '/media/data/musique/creation/Mega Combi/MegaCombi241-PT134-24062015_Comme_des_lyca_ens.mp3')
 | 
			
		||||
 | 
			
		||||
# start the server on monitor if not present
 | 
			
		||||
ensure('AIRCOX_LIQUIDSOAP_AUTOSTART', True)
 | 
			
		||||
 | 
			
		||||
# 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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										134
									
								
								liquidsoap/templates/aircox/liquidsoap/controller.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								liquidsoap/templates/aircox/liquidsoap/controller.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,134 @@
 | 
			
		||||
{% if not embed %}
 | 
			
		||||
<html>
 | 
			
		||||
    <head>
 | 
			
		||||
        <style>
 | 
			
		||||
        .station {
 | 
			
		||||
            margin: 2em;
 | 
			
		||||
            border: 1px grey solid;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            .sources {
 | 
			
		||||
                padding: 0.5em;
 | 
			
		||||
                box-shadow: inset 0.1em 0.1em 0.5em rgba(0, 0, 0, 0.5);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        .station h1 {
 | 
			
		||||
            font-size: 1.2em;
 | 
			
		||||
            margin: 0.2em;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .source {
 | 
			
		||||
            border-left: 0.5em solid grey;
 | 
			
		||||
            font-size: 0.9em;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            .on_air {
 | 
			
		||||
                display: block;
 | 
			
		||||
                border-left: 0.5em solid #f00;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .source h2 {
 | 
			
		||||
                display: inline-block;
 | 
			
		||||
                min-width: 10em;
 | 
			
		||||
                font-size: 1em;
 | 
			
		||||
                margin: 0.2em;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .source time {
 | 
			
		||||
                display: inline-block;
 | 
			
		||||
                margin-right: 2em;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .source span {
 | 
			
		||||
                font-size: 1em;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        .error {
 | 
			
		||||
            padding: 0.2em;
 | 
			
		||||
            color: red;
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
        }
 | 
			
		||||
        </style>
 | 
			
		||||
 | 
			
		||||
        <script>
 | 
			
		||||
 | 
			
		||||
            function get_token() {
 | 
			
		||||
                return document.cookie.replace(/.*csrftoken=([^;]+)(;.*|$)/, '$1');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function liquid_action (controller, source, action) {
 | 
			
		||||
                params = 'controller=' + controller + '&&source=' + source +
 | 
			
		||||
                         '&&action=' + action;
 | 
			
		||||
 | 
			
		||||
                req = new XMLHttpRequest()
 | 
			
		||||
                req.open('POST', '{% url 'liquid-controller' %}', false);
 | 
			
		||||
                req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
 | 
			
		||||
                req.setRequestHeader("Content-length", params.length);
 | 
			
		||||
                req.setRequestHeader("Connection", "close");
 | 
			
		||||
                req.setRequestHeader("X-CSRFToken", get_token());
 | 
			
		||||
 | 
			
		||||
                req.send(params);
 | 
			
		||||
                liquid_update()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function liquid_update (update) {
 | 
			
		||||
                req = new XMLHttpRequest()
 | 
			
		||||
                req.open('GET', '{% url 'liquid-controller' %}?embed', true);
 | 
			
		||||
 | 
			
		||||
                req.onreadystatechange = function() {
 | 
			
		||||
                    if(req.readyState != 4 || (req.status != 200 && req.status != 0))
 | 
			
		||||
                        return;
 | 
			
		||||
                    document.getElementById('liquid-stations').innerHTML =
 | 
			
		||||
                        req.responseText;
 | 
			
		||||
 | 
			
		||||
                    if(update)
 | 
			
		||||
                        window.setTimeout(function() { liquid_update(update);}, 5000);
 | 
			
		||||
                };
 | 
			
		||||
                req.send();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            liquid_update(true);
 | 
			
		||||
        </script>
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <main id="liquid-stations">
 | 
			
		||||
{% endif %}
 | 
			
		||||
            {% for c_id, controller in monitor.controllers.items %}
 | 
			
		||||
            {% with on_air=controller.on_air %}
 | 
			
		||||
            <div id="{{ c_id }}" class="station">
 | 
			
		||||
                <header>
 | 
			
		||||
                    {% if not controller.connector.available %}
 | 
			
		||||
                    <span class="error" style="float:right;">disconnected</span>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <h1>
 | 
			
		||||
                        {{ controller.station.name }}
 | 
			
		||||
                    </h1>
 | 
			
		||||
                </header>
 | 
			
		||||
                <div class="sources">
 | 
			
		||||
                    {% with source=controller.master %}
 | 
			
		||||
                    {% include 'aircox_liquidsoap/source.html' %}
 | 
			
		||||
                    {% endwith %}
 | 
			
		||||
 | 
			
		||||
                    {% with source=controller.dealer %}
 | 
			
		||||
                    {% include 'aircox_liquidsoap/source.html' %}
 | 
			
		||||
                    {% endwith %}
 | 
			
		||||
 | 
			
		||||
                    {% for source in controller.streams.values %}
 | 
			
		||||
                    {% include 'aircox_liquidsoap/source.html' %}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="next">
 | 
			
		||||
                    {% for diffusion in controller.next_diffusions %}
 | 
			
		||||
                    {{ diffusion }}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endwith %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
 | 
			
		||||
{% if not embed %}
 | 
			
		||||
        </main>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								liquidsoap/templates/aircox/liquidsoap/source.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								liquidsoap/templates/aircox/liquidsoap/source.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
{% with metadata=source.metadata %}
 | 
			
		||||
<div class="source {% if metadata.initial_uri == controller.master.metadata.initial_uri %}on_air{% endif %}">
 | 
			
		||||
    <h2>{{ source.name }}</h2>
 | 
			
		||||
    <time>{{ metadata.on_air }}</time>
 | 
			
		||||
    <span class="path">{{ metadata.initial_uri }}</span>
 | 
			
		||||
    <span class="status" status="{{ metadata.status }}">{{ metadata.status }}</span>
 | 
			
		||||
 | 
			
		||||
    <button onclick="liquid_action('{{controller.id}}','{{source.id}}','skip');">
 | 
			
		||||
        skip
 | 
			
		||||
    </button>
 | 
			
		||||
</div>
 | 
			
		||||
{% endwith %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										81
									
								
								liquidsoap/templates/aircox/liquidsoap/station.liq
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								liquidsoap/templates/aircox/liquidsoap/station.liq
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
{# Context: #}
 | 
			
		||||
{# - controller: controller used to generate the current file #}
 | 
			
		||||
{# - settings: global settings #}
 | 
			
		||||
def interactive_source (id, s) = \
 | 
			
		||||
    def handler(m) = \
 | 
			
		||||
        file = string.escape(m['filename']) \
 | 
			
		||||
        system('{{ log_script }} -s "#{id}" -p "#{file}" -c "liquidsoap: play" &') \
 | 
			
		||||
    end \
 | 
			
		||||
    \
 | 
			
		||||
    s = on_track(id=id, handler, s)
 | 
			
		||||
    # s = store_metadata(id=id, size=1, s) \
 | 
			
		||||
    add_skip_command(s) \
 | 
			
		||||
    s \
 | 
			
		||||
end \
 | 
			
		||||
\
 | 
			
		||||
def stream (id, file) = \
 | 
			
		||||
    s = playlist(id = '#{id}_playlist', mode = "random", \
 | 
			
		||||
                 reload_mode='watch', file) \
 | 
			
		||||
    interactive_source(id, s) \
 | 
			
		||||
end \
 | 
			
		||||
\
 | 
			
		||||
{# Config #}
 | 
			
		||||
set("server.socket", true) \
 | 
			
		||||
set("server.socket.path", "{{ controller.socket_path }}") \
 | 
			
		||||
{% for key, value in settings.AIRCOX_LIQUIDSOAP_SET.items %}
 | 
			
		||||
set("{{ key|safe }}", {{ value|safe }}) \
 | 
			
		||||
{% endfor %}
 | 
			
		||||
\
 | 
			
		||||
{# station #}
 | 
			
		||||
{{ controller.id }} = interactive_source ( \
 | 
			
		||||
    "{{ controller.id }}", \
 | 
			
		||||
    fallback(track_sensitive = false, [ \
 | 
			
		||||
        {# dealer #}
 | 
			
		||||
        {% with source=controller.dealer %}
 | 
			
		||||
        {% if source %}
 | 
			
		||||
        at(interactive.bool('{{ source.id }}_on', false), \
 | 
			
		||||
            interactive_source('{{ source.id }}', playlist.once( \
 | 
			
		||||
                reload_mode='watch', \
 | 
			
		||||
                "{{ source.path }}", \
 | 
			
		||||
            )) \
 | 
			
		||||
        ), \
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% endwith %}
 | 
			
		||||
 | 
			
		||||
        {# streams #}
 | 
			
		||||
        interactive_source("{{ controller.id }}_streams", rotate([ \
 | 
			
		||||
            {% for source in controller.streams.values %}
 | 
			
		||||
            {% with info=source.stream_info %}
 | 
			
		||||
                {% if info.delay %}
 | 
			
		||||
            delay({{ info.delay }}., stream("{{ source.id }}", "{{ source.path }}")), \
 | 
			
		||||
                {% elif info.begin and info.end %}
 | 
			
		||||
            at({ {{info.begin}}-{{info.end}} }, stream("{{ source.id }}", "{{ source.path }}")), \
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            {% endwith %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
 | 
			
		||||
            {% for source in controller.streams.values %}
 | 
			
		||||
                {% if not source.stream_info %}
 | 
			
		||||
            stream("{{ source.id }}", "{{ source.path }}"), \
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        ])), \
 | 
			
		||||
 | 
			
		||||
        {# fallback #}
 | 
			
		||||
        {% if controller.station.fallback %}
 | 
			
		||||
            single("{{ controller.station.fallback }}"), \
 | 
			
		||||
        {% else %}
 | 
			
		||||
            blank(), \
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    ]) \
 | 
			
		||||
) \
 | 
			
		||||
\
 | 
			
		||||
{% for output in controller.outputs %}
 | 
			
		||||
output.{{ output.get_type_display }}( \
 | 
			
		||||
   {{ controller.id }}
 | 
			
		||||
    {% if controller.settings %}, \
 | 
			
		||||
    {{ controller.settings }}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
) \
 | 
			
		||||
{% endfor %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								liquidsoap/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								liquidsoap/tests.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
 | 
			
		||||
# Create your tests here.
 | 
			
		||||
							
								
								
									
										9
									
								
								liquidsoap/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								liquidsoap/urls.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
from django.conf.urls import url
 | 
			
		||||
 | 
			
		||||
import aircox.liquidsoap.views as views
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url('^controller/', views.LiquidControl.as_view(),  name = 'liquid-controller'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										411
									
								
								liquidsoap/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										411
									
								
								liquidsoap/utils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,411 @@
 | 
			
		||||
import os
 | 
			
		||||
import socket
 | 
			
		||||
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 programs
 | 
			
		||||
 | 
			
		||||
import aircox.liquidsoap.models as models
 | 
			
		||||
import aircox.liquidsoap.settings as settings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Connector:
 | 
			
		||||
    """
 | 
			
		||||
    Telnet connector utility.
 | 
			
		||||
 | 
			
		||||
    address: a string to the unix domain socket file, or a tuple
 | 
			
		||||
        (host, port) for TCP/IP connection
 | 
			
		||||
    """
 | 
			
		||||
    __socket = None
 | 
			
		||||
    __available = False
 | 
			
		||||
    address = None
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def available (self):
 | 
			
		||||
        return self.__available
 | 
			
		||||
 | 
			
		||||
    def __init__ (self, address = None):
 | 
			
		||||
        if address:
 | 
			
		||||
            self.address = address
 | 
			
		||||
 | 
			
		||||
    def open (self):
 | 
			
		||||
        if self.__available:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            family = socket.AF_INET if type(self.address) in (tuple, list) else \
 | 
			
		||||
                     socket.AF_UNIX
 | 
			
		||||
            self.__socket = socket.socket(family, socket.SOCK_STREAM)
 | 
			
		||||
            self.__socket.connect(self.address)
 | 
			
		||||
            self.__available = True
 | 
			
		||||
        except:
 | 
			
		||||
            # print('can not connect to liquidsoap socket {}'.format(self.address))
 | 
			
		||||
            self.__available = False
 | 
			
		||||
            return -1
 | 
			
		||||
 | 
			
		||||
    def send (self, *data, try_count = 1, parse = False, parse_json = False):
 | 
			
		||||
        if self.open():
 | 
			
		||||
            return ''
 | 
			
		||||
        data = bytes(''.join([str(d) for d in data]) + '\n', encoding='utf-8')
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            reg = re.compile(r'(.*)\s+END\s*$')
 | 
			
		||||
            self.__socket.sendall(data)
 | 
			
		||||
            data = ''
 | 
			
		||||
            while not reg.search(data):
 | 
			
		||||
                data += self.__socket.recv(1024).decode('unicode_escape')
 | 
			
		||||
 | 
			
		||||
            if data:
 | 
			
		||||
                data = reg.sub(r'\1', data)
 | 
			
		||||
                data = data.strip()
 | 
			
		||||
 | 
			
		||||
                if parse:
 | 
			
		||||
                    data = self.parse(data)
 | 
			
		||||
                elif parse_json:
 | 
			
		||||
                    data = self.parse_json(data)
 | 
			
		||||
            return data
 | 
			
		||||
        except:
 | 
			
		||||
            self.__available = False
 | 
			
		||||
            if try_count > 0:
 | 
			
		||||
                return self.send(data, try_count - 1)
 | 
			
		||||
 | 
			
		||||
    def parse (self, string):
 | 
			
		||||
        string = string.split('\n')
 | 
			
		||||
        data = {}
 | 
			
		||||
        for line in string:
 | 
			
		||||
            line = re.search(r'(?P<key>[^=]+)="?(?P<value>([^"]|\\")+)"?', line)
 | 
			
		||||
            if not line:
 | 
			
		||||
                continue
 | 
			
		||||
            line = line.groupdict()
 | 
			
		||||
            data[line['key']] = line['value']
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
    def parse_json (self, string):
 | 
			
		||||
        try:
 | 
			
		||||
            if string[0] == '"' and string[-1] == '"':
 | 
			
		||||
                string = string[1:-1]
 | 
			
		||||
            return json.loads(string) if string else None
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Source:
 | 
			
		||||
    """
 | 
			
		||||
    A structure that holds informations about a LiquidSoap source.
 | 
			
		||||
    """
 | 
			
		||||
    controller = None
 | 
			
		||||
    program = None
 | 
			
		||||
    metadata = None
 | 
			
		||||
 | 
			
		||||
    def __init__ (self, controller = None, program = None):
 | 
			
		||||
        self.controller = controller
 | 
			
		||||
        self.program = program
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def station (self):
 | 
			
		||||
        """
 | 
			
		||||
        Proxy to self.(program|controller).station
 | 
			
		||||
        """
 | 
			
		||||
        return self.program.station if self.program else \
 | 
			
		||||
                self.controller.station
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def connector (self):
 | 
			
		||||
        """
 | 
			
		||||
        Proxy to self.controller.connector
 | 
			
		||||
        """
 | 
			
		||||
        return self.controller.connector
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def id (self):
 | 
			
		||||
        """
 | 
			
		||||
        Identifier for the source, scoped in the station's one
 | 
			
		||||
        """
 | 
			
		||||
        postfix = ('_stream_' + str(self.program.id)) if self.program else ''
 | 
			
		||||
        return self.station.slug + postfix
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def name (self):
 | 
			
		||||
        """
 | 
			
		||||
        Name of the related object (program or station)
 | 
			
		||||
        """
 | 
			
		||||
        if self.program:
 | 
			
		||||
            return self.program.name
 | 
			
		||||
        return self.station.name
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def path (self):
 | 
			
		||||
        """
 | 
			
		||||
        Path to the playlist
 | 
			
		||||
        """
 | 
			
		||||
        return os.path.join(
 | 
			
		||||
            settings.AIRCOX_LIQUIDSOAP_MEDIA,
 | 
			
		||||
            self.station.slug,
 | 
			
		||||
            self.id + '.m3u'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def playlist (self):
 | 
			
		||||
        """
 | 
			
		||||
        Get or set the playlist as an array, and update it into
 | 
			
		||||
        the corresponding file.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            with open(self.path, 'r') as file:
 | 
			
		||||
                return file.readlines()
 | 
			
		||||
        except:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
    @playlist.setter
 | 
			
		||||
    def playlist (self, sounds):
 | 
			
		||||
        with open(self.path, 'w') as file:
 | 
			
		||||
            file.write('\n'.join(sounds))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def current_sound (self):
 | 
			
		||||
        self.update()
 | 
			
		||||
        return self.metadata.get('initial_uri') if self.metadata else {}
 | 
			
		||||
 | 
			
		||||
    def stream_info (self):
 | 
			
		||||
        """
 | 
			
		||||
        Return a dict with info related to the program's stream
 | 
			
		||||
        """
 | 
			
		||||
        if not self.program:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        stream = programs.Stream.objects.get(program = self.program)
 | 
			
		||||
        if not stream.begin and not stream.delay:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        def to_seconds (time):
 | 
			
		||||
            return 3600 * time.hour + 60 * time.minute + time.second
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'begin': stream.begin.strftime('%Hh%M') if stream.begin else None,
 | 
			
		||||
            'end': stream.end.strftime('%Hh%M') if stream.end else None,
 | 
			
		||||
            'delay': to_seconds(stream.delay) if stream.delay else None
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def skip (self):
 | 
			
		||||
        """
 | 
			
		||||
        Skip a given source. If no source, use master.
 | 
			
		||||
        """
 | 
			
		||||
        self.connector.send(self.id, '.skip')
 | 
			
		||||
 | 
			
		||||
    def update (self, metadata = None):
 | 
			
		||||
        """
 | 
			
		||||
        Update metadata with the given metadata dict or request them to
 | 
			
		||||
        liquidsoap if nothing is given.
 | 
			
		||||
 | 
			
		||||
        Return -1 in case no update happened
 | 
			
		||||
        """
 | 
			
		||||
        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)
 | 
			
		||||
        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):
 | 
			
		||||
    """
 | 
			
		||||
    The Dealer source is a source that is used for scheduled diffusions and
 | 
			
		||||
    manual sound diffusion.
 | 
			
		||||
 | 
			
		||||
    Since we need to cache buffers for the scheduled track, we use an on-off
 | 
			
		||||
    switch in order to have no latency and enable preload.
 | 
			
		||||
    """
 | 
			
		||||
    name = _('Dealer')
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def id (self):
 | 
			
		||||
        return self.station.slug + '_dealer'
 | 
			
		||||
 | 
			
		||||
    def stream_info (self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def on (self):
 | 
			
		||||
        """
 | 
			
		||||
        Switch on-off;
 | 
			
		||||
        """
 | 
			
		||||
        r = self.connector.send('var.get ', self.id, '_on')
 | 
			
		||||
        return (r == 'true')
 | 
			
		||||
 | 
			
		||||
    @on.setter
 | 
			
		||||
    def on (self, value):
 | 
			
		||||
        return self.connector.send('var.set ', self.id, '_on',
 | 
			
		||||
                                    '=', 'true' if value else 'false')
 | 
			
		||||
 | 
			
		||||
    def __get_next (self, date, on_air):
 | 
			
		||||
        """
 | 
			
		||||
        Return which diffusion should be played now and is not playing
 | 
			
		||||
        """
 | 
			
		||||
        r = [ programs.Diffusion.get_prev(self.station, date),
 | 
			
		||||
              programs.Diffusion.get_next(self.station, date) ]
 | 
			
		||||
        r = [ diffusion.prefetch_related('sounds')[0]
 | 
			
		||||
                for diffusion in r if diffusion.count() ]
 | 
			
		||||
 | 
			
		||||
        for diffusion in r:
 | 
			
		||||
            duration = to_timedelta(diffusion.archives_duration())
 | 
			
		||||
            end_at = diffusion.date + duration
 | 
			
		||||
            if end_at < date:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            diffusion.playlist = [ sound.path
 | 
			
		||||
                                    for sound in diffusion.get_archives() ]
 | 
			
		||||
            if diffusion.playlist and on_air not in diffusion.playlist:
 | 
			
		||||
                return diffusion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
    dealer = None       # dealer source
 | 
			
		||||
    streams = None      # streams streams
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def on_air (self):
 | 
			
		||||
        return self.master
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def id (self):
 | 
			
		||||
        return self.master and self.master.id
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def name (self):
 | 
			
		||||
        return self.master and self.master.name
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def path (self):
 | 
			
		||||
        """
 | 
			
		||||
        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.station = station
 | 
			
		||||
        self.station.controller = self
 | 
			
		||||
        self.outputs = models.Output.objects.filter(station = station)
 | 
			
		||||
 | 
			
		||||
        self.connector = connector and Connector(self.socket_path)
 | 
			
		||||
 | 
			
		||||
        self.master = Master(self)
 | 
			
		||||
        self.dealer = Dealer(self)
 | 
			
		||||
        self.streams = {
 | 
			
		||||
            source.id : source
 | 
			
		||||
            for source in [
 | 
			
		||||
                Source(self, program)
 | 
			
		||||
                for program in programs.Program.objects.filter(station = station,
 | 
			
		||||
                                                             active = True)
 | 
			
		||||
                if program.stream_set.count()
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def get (self, source_id):
 | 
			
		||||
        """
 | 
			
		||||
        Get a source by its id
 | 
			
		||||
        """
 | 
			
		||||
        if source_id == self.master.id:
 | 
			
		||||
            return self.master
 | 
			
		||||
        if source_id == self.dealer.id:
 | 
			
		||||
            return self.dealer
 | 
			
		||||
        return self.streams.get(source_id)
 | 
			
		||||
 | 
			
		||||
    def log (self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Create a log using **kwargs, and print info
 | 
			
		||||
        """
 | 
			
		||||
        log = programs.Log(**kwargs)
 | 
			
		||||
        log.save()
 | 
			
		||||
        log.print()
 | 
			
		||||
 | 
			
		||||
    def update_all (self):
 | 
			
		||||
        """
 | 
			
		||||
        Fetch and update all streams metadata.
 | 
			
		||||
        """
 | 
			
		||||
        self.master.update()
 | 
			
		||||
        self.dealer.update()
 | 
			
		||||
        for source in self.streams.values():
 | 
			
		||||
            source.update()
 | 
			
		||||
 | 
			
		||||
    def monitor (self):
 | 
			
		||||
        """
 | 
			
		||||
        Log changes in the streams, and call dealer.monitor.
 | 
			
		||||
        """
 | 
			
		||||
        if not self.connector.available and self.connector.open():
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self.dealer.monitor()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Monitor:
 | 
			
		||||
    """
 | 
			
		||||
    Monitor multiple controllers.
 | 
			
		||||
    """
 | 
			
		||||
    controllers = None
 | 
			
		||||
 | 
			
		||||
    def __init__ (self):
 | 
			
		||||
        self.controllers = {
 | 
			
		||||
            controller.id : controller
 | 
			
		||||
            for controller in [
 | 
			
		||||
                Controller(station, True)
 | 
			
		||||
                for station in programs.Station.objects.filter(active = True)
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def update (self):
 | 
			
		||||
        for controller in self.controllers.values():
 | 
			
		||||
            controller.update_all()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										65
									
								
								liquidsoap/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								liquidsoap/views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from django.views.generic.base import View, TemplateResponseMixin
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
from django.http import HttpResponse
 | 
			
		||||
 | 
			
		||||
import aircox.liquidsoap.settings as settings
 | 
			
		||||
import aircox.liquidsoap.utils as utils
 | 
			
		||||
import aircox.programs.models as models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
view_monitor = None
 | 
			
		||||
 | 
			
		||||
def get_monitor():
 | 
			
		||||
    global view_monitor
 | 
			
		||||
    if not view_monitor:
 | 
			
		||||
        view_monitor = utils.Monitor()
 | 
			
		||||
    return view_monitor
 | 
			
		||||
 | 
			
		||||
class Actions:
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def exec (cl, monitor, controller, source, action):
 | 
			
		||||
        controller = monitor.controllers.get(controller)
 | 
			
		||||
        source = controller and controller.get(source)
 | 
			
		||||
 | 
			
		||||
        if not controller or not source or \
 | 
			
		||||
                action.startswith('__') or \
 | 
			
		||||
                action not in cl.__dict__:
 | 
			
		||||
            return -1
 | 
			
		||||
 | 
			
		||||
        action = getattr(Actions, action)
 | 
			
		||||
        return action(monitor, controller, source)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def skip (cl, monitor, controller, source):
 | 
			
		||||
        source.skip()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LiquidControl (View):
 | 
			
		||||
    template_name = 'aircox/liquidsoap/controller.html'
 | 
			
		||||
 | 
			
		||||
    def get_context_data (self, **kwargs):
 | 
			
		||||
        get_monitor().update()
 | 
			
		||||
        return {
 | 
			
		||||
            'request': self.request,
 | 
			
		||||
            'monitor': get_monitor(),
 | 
			
		||||
            'embed': 'embed' in self.request.GET,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def post (self, request = None, **kwargs):
 | 
			
		||||
        if 'action' in request.POST:
 | 
			
		||||
            POST = request.POST
 | 
			
		||||
            controller = POST.get('controller')
 | 
			
		||||
            source = POST.get('source')
 | 
			
		||||
            action = POST.get('action')
 | 
			
		||||
            Actions.exec(get_monitor(), controller, source, action)
 | 
			
		||||
        return HttpResponse('')
 | 
			
		||||
 | 
			
		||||
    def get (self, request = None, **kwargs):
 | 
			
		||||
        self.request = request
 | 
			
		||||
        context = self.get_context_data(**kwargs)
 | 
			
		||||
        return render(request, self.template_name, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user