@ -5,12 +5,69 @@ from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sounds_info = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_sounds_info(apps, schema_editor):
 | 
			
		||||
    global sounds
 | 
			
		||||
 | 
			
		||||
    Sound = apps.get_model("aircox", "Sound")
 | 
			
		||||
    objs = Sound.objects.filter(episode__isnull=False).values(
 | 
			
		||||
        "pk",
 | 
			
		||||
        "episode_id",
 | 
			
		||||
        "position",
 | 
			
		||||
        "type",
 | 
			
		||||
    )
 | 
			
		||||
    sounds_info.update({obj["pk"]: obj for obj in objs})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def restore_sounds_info(apps, schema_editor):
 | 
			
		||||
    global sounds
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        Sound = apps.get_model("aircox", "Sound")
 | 
			
		||||
        EpisodeSound = apps.get_model("aircox", "EpisodeSound")
 | 
			
		||||
        TYPE_ARCHIVE = 0x01
 | 
			
		||||
        TYPE_REMOVED = 0x03
 | 
			
		||||
 | 
			
		||||
        episode_sounds = []
 | 
			
		||||
        sounds = []
 | 
			
		||||
        for sound in Sound.objects.all():
 | 
			
		||||
            info = sounds_info.get(sound.pk)
 | 
			
		||||
            if not info:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            sound.broadcast = info["type"] == TYPE_ARCHIVE
 | 
			
		||||
            sound.is_removed = info["type"] == TYPE_REMOVED
 | 
			
		||||
            sounds.append(sound)
 | 
			
		||||
            if not sound.is_removed:
 | 
			
		||||
                obj = EpisodeSound(
 | 
			
		||||
                    sound=sound,
 | 
			
		||||
                    episode_id=info["episode_id"],
 | 
			
		||||
                    position=info["position"],
 | 
			
		||||
                    broadcast=sound.broadcast,
 | 
			
		||||
                )
 | 
			
		||||
                episode_sounds.append(obj)
 | 
			
		||||
 | 
			
		||||
        Sound.objects.bulk_update(sounds, ("broadcast", "is_removed"))
 | 
			
		||||
        EpisodeSound.objects.bulk_create(episode_sounds)
 | 
			
		||||
 | 
			
		||||
        print(f"\n>>> {len(sounds)} Sound have been updated.")
 | 
			
		||||
        print(f">>> {len(episode_sounds)} EpisodeSound have been created.")
 | 
			
		||||
    except Exception as err:
 | 
			
		||||
        print(err)
 | 
			
		||||
        import traceback
 | 
			
		||||
 | 
			
		||||
        traceback.print_exc()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("aircox", "0025_sound_is_removed_alter_sound_is_downloadable_and_more"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RunPython(get_sounds_info),
 | 
			
		||||
        migrations.AlterModelOptions(
 | 
			
		||||
            name="sound",
 | 
			
		||||
            options={"verbose_name": "Sound file", "verbose_name_plural": "Sound files"},
 | 
			
		||||
@ -105,4 +162,5 @@ class Migration(migrations.Migration):
 | 
			
		||||
                "verbose_name_plural": "Episode Sounds",
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RunPython(restore_sounds_info),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,8 @@
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils.functional import cached_property
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from filer.fields.image import FilerImageField
 | 
			
		||||
 | 
			
		||||
from aircox.conf import settings
 | 
			
		||||
 | 
			
		||||
__all__ = ("Station", "StationQuerySet", "Port")
 | 
			
		||||
 | 
			
		||||
@ -32,13 +29,6 @@ class Station(models.Model):
 | 
			
		||||
 | 
			
		||||
    name = models.CharField(_("name"), max_length=64)
 | 
			
		||||
    slug = models.SlugField(_("slug"), max_length=64, unique=True)
 | 
			
		||||
    # FIXME: remove - should be decided only by Streamer controller + settings
 | 
			
		||||
    path = models.CharField(
 | 
			
		||||
        _("path"),
 | 
			
		||||
        help_text=_("path to the working directory"),
 | 
			
		||||
        max_length=256,
 | 
			
		||||
        blank=True,
 | 
			
		||||
    )
 | 
			
		||||
    default = models.BooleanField(
 | 
			
		||||
        _("default station"),
 | 
			
		||||
        default=False,
 | 
			
		||||
@ -96,12 +86,6 @@ class Station(models.Model):
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
    def save(self, make_sources=True, *args, **kwargs):
 | 
			
		||||
        if not self.path:
 | 
			
		||||
            self.path = os.path.join(
 | 
			
		||||
                settings.CONTROLLERS_WORKING_DIR,
 | 
			
		||||
                self.slug.replace("-", "_"),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if self.default:
 | 
			
		||||
            qs = Station.objects.filter(default=True)
 | 
			
		||||
            if self.pk is not None:
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ class AdminMixin(LoginRequiredMixin, UserPassesTestMixin):
 | 
			
		||||
        return self.request.station
 | 
			
		||||
 | 
			
		||||
    def test_func(self):
 | 
			
		||||
        return self.request.user.is_staff
 | 
			
		||||
        return self.request.user.is_admin
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs.update(admin.site.each_context(self.request))
 | 
			
		||||
 | 
			
		||||
@ -77,9 +77,12 @@ class Metadata:
 | 
			
		||||
            air_time = tz.datetime.strptime(air_time, "%Y/%m/%d %H:%M:%S")
 | 
			
		||||
            return local_tz.localize(air_time)
 | 
			
		||||
 | 
			
		||||
    def validate(self, data):
 | 
			
		||||
    def validate(self, data, as_dict=False):
 | 
			
		||||
        """Validate provided data and set as attribute (must already be
 | 
			
		||||
        declared)"""
 | 
			
		||||
        if as_dict and isinstance(data, list):
 | 
			
		||||
            data = {v[0]: v[1] for v in data}
 | 
			
		||||
 | 
			
		||||
        for key, value in data.items():
 | 
			
		||||
            if hasattr(self, key) and not callable(getattr(self, key)):
 | 
			
		||||
                setattr(self, key, value)
 | 
			
		||||
 | 
			
		||||
@ -43,9 +43,9 @@ class Source(Metadata):
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            self.remaining = None
 | 
			
		||||
 | 
			
		||||
        data = self.controller.send(self.id, ".get", parse=True)
 | 
			
		||||
        data = self.controller.send(f"var.get {self.id}_meta", parse_json=True)
 | 
			
		||||
        if data:
 | 
			
		||||
            self.validate(data if data and isinstance(data, dict) else {})
 | 
			
		||||
            self.validate(data if data and isinstance(data, (dict, list)) else {}, as_dict=True)
 | 
			
		||||
 | 
			
		||||
    def skip(self):
 | 
			
		||||
        """Skip the current source sound."""
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,7 @@ import subprocess
 | 
			
		||||
import psutil
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
 | 
			
		||||
from aircox.conf import settings
 | 
			
		||||
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
from ..connector import Connector
 | 
			
		||||
from .sources import PlaylistSource, QueueSource
 | 
			
		||||
 | 
			
		||||
@ -46,8 +45,8 @@ class Streamer:
 | 
			
		||||
        self.outputs = self.station.port_set.active().output()
 | 
			
		||||
 | 
			
		||||
        self.id = self.station.slug.replace("-", "_")
 | 
			
		||||
        self.path = os.path.join(station.path, "station.liq")
 | 
			
		||||
        self.connector = connector or Connector(os.path.join(station.path, "station.sock"))
 | 
			
		||||
        self.path = settings.get_dir(station, "station.liq")
 | 
			
		||||
        self.connector = connector or Connector(settings.get_dir(station, "station.sock"))
 | 
			
		||||
        self.init_sources()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@ -98,7 +97,6 @@ class Streamer:
 | 
			
		||||
            {
 | 
			
		||||
                "station": self.station,
 | 
			
		||||
                "streamer": self,
 | 
			
		||||
                "settings": settings,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        data = re.sub("[\t ]+\n", "\n", data)
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ from aircox_streamer.controllers import Monitor, Streamer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# force using UTC
 | 
			
		||||
tz.activate(timezone.UTC)
 | 
			
		||||
tz.activate(timezone.utc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Command(BaseCommand):
 | 
			
		||||
 | 
			
		||||
@ -10,9 +10,9 @@ Base liquidsoap station configuration.
 | 
			
		||||
 | 
			
		||||
{% block functions %}
 | 
			
		||||
{# Seek function #}
 | 
			
		||||
def seek(source, t) =
 | 
			
		||||
def seek(s, t) =
 | 
			
		||||
  t = float_of_string(default=0.,t)
 | 
			
		||||
  ret = source.seek(source,t)
 | 
			
		||||
  ret = source.seek(s,t)
 | 
			
		||||
  log("seek #{ret} seconds.")
 | 
			
		||||
  "#{ret}"
 | 
			
		||||
end
 | 
			
		||||
@ -30,6 +30,17 @@ def to_stream(live, stream)
 | 
			
		||||
  add(normalize=false, [live,stream])
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
{# Skip command #}
 | 
			
		||||
def add_skip_command(s) =
 | 
			
		||||
    def skip(_) =
 | 
			
		||||
        source.skip(s)
 | 
			
		||||
        "Done!"
 | 
			
		||||
    end
 | 
			
		||||
    server.register(namespace="#{source.id(s)}",
 | 
			
		||||
        usage="skip",
 | 
			
		||||
        description="Skip the current song.",
 | 
			
		||||
        "skip",skip)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
{% comment %}
 | 
			
		||||
An interactive source is a source that:
 | 
			
		||||
@ -45,10 +56,14 @@ def interactive (id, s) =
 | 
			
		||||
    server.register(namespace=id,
 | 
			
		||||
                    description="Get source's track remaining time",
 | 
			
		||||
                    usage="remaining",
 | 
			
		||||
                    "remaining", fun (_) ->  begin json_of(source.remaining(s)) end)
 | 
			
		||||
                    "remaining", fun (_) ->  begin json.stringify(source.remaining(s)) end)
 | 
			
		||||
 | 
			
		||||
    s = store_metadata(id=id, size=1, s)
 | 
			
		||||
    add_skip_command(s)
 | 
			
		||||
 | 
			
		||||
    {# metadata: create an interactive variable as "{id}_meta" #}
 | 
			
		||||
    s_meta = interactive.string("#{id}_meta", "")
 | 
			
		||||
    s = source.on_metadata(s, fun(meta) -> s_meta.set(json.stringify(meta)))
 | 
			
		||||
 | 
			
		||||
    s
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -66,9 +81,6 @@ end
 | 
			
		||||
set("server.socket", true)
 | 
			
		||||
set("server.socket.path", "{{ streamer.socket_path }}")
 | 
			
		||||
set("log.file.path", "{{ station.path }}/liquidsoap.log")
 | 
			
		||||
{% for key, value in settings.AIRCOX_LIQUIDSOAP_SET.items %}
 | 
			
		||||
set("{{ key|safe }}", {{ value|safe }})
 | 
			
		||||
{% endfor %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block config_extras %}
 | 
			
		||||
 | 
			
		||||
@ -20,13 +20,18 @@ from django.contrib import admin
 | 
			
		||||
from django.urls import include, path
 | 
			
		||||
 | 
			
		||||
import aircox.urls
 | 
			
		||||
import aircox_streamer.urls
 | 
			
		||||
 | 
			
		||||
urlpatterns = aircox.urls.urls + [
 | 
			
		||||
urlpatterns = (
 | 
			
		||||
    aircox.urls.urls
 | 
			
		||||
    + aircox_streamer.urls.urls
 | 
			
		||||
    + [
 | 
			
		||||
        path("admin/", admin.site.urls),
 | 
			
		||||
        path("accounts/", include("django.contrib.auth.urls")),
 | 
			
		||||
        path("ckeditor/", include("ckeditor_uploader.urls")),
 | 
			
		||||
        path("filer/", include("filer.urls")),
 | 
			
		||||
    ]
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if settings.DEBUG:
 | 
			
		||||
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user