WIP - Sound.file instead of Sound.path; fix issues with player; program.path is now relative
This commit is contained in:
parent
d17d6831dd
commit
e3b744be70
|
@ -28,7 +28,7 @@ class SoundInline(admin.TabularInline):
|
||||||
max_num = 0
|
max_num = 0
|
||||||
|
|
||||||
def audio(self, obj):
|
def audio(self, obj):
|
||||||
return mark_safe('<audio src="{}" controls></audio>'.format(obj.url()))
|
return mark_safe('<audio src="{}" controls></audio>'.format(obj.file.url))
|
||||||
audio.short_descripton = _('Audio')
|
audio.short_descripton = _('Audio')
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
|
@ -46,10 +46,10 @@ class SoundAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
search_fields = ['name', 'program__title']
|
search_fields = ['name', 'program__title']
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None, {'fields': ['name', 'path', 'type', 'program', 'episode']}),
|
(None, {'fields': ['name', 'file', 'type', 'program', 'episode']}),
|
||||||
(None, {'fields': ['duration', 'is_public', 'is_good_quality', 'mtime']}),
|
(None, {'fields': ['duration', 'is_public', 'is_good_quality', 'mtime']}),
|
||||||
]
|
]
|
||||||
readonly_fields = ('path', 'duration',)
|
readonly_fields = ('file', 'duration',)
|
||||||
inlines = [SoundTrackInline]
|
inlines = [SoundTrackInline]
|
||||||
|
|
||||||
def related(self, obj):
|
def related(self, obj):
|
||||||
|
@ -59,7 +59,7 @@ class SoundAdmin(admin.ModelAdmin):
|
||||||
related.short_description = _('Program / Episode')
|
related.short_description = _('Program / Episode')
|
||||||
|
|
||||||
def audio(self, obj):
|
def audio(self, obj):
|
||||||
return mark_safe('<audio src="{}" controls></audio>'.format(obj.url()))
|
return mark_safe('<audio src="{}" controls></audio>'.format(obj.file.url))
|
||||||
audio.short_descripton = _('Audio')
|
audio.short_descripton = _('Audio')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ class AdminSite(admin.AdminSite):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
urls = super().get_urls() + [
|
urls = [
|
||||||
path('api/', include((self.router.urls, 'api'))),
|
path('api/', include((self.router.urls, 'api'))),
|
||||||
path('tools/statistics/',
|
path('tools/statistics/',
|
||||||
self.admin_view(StatisticsView.as_view()),
|
self.admin_view(StatisticsView.as_view()),
|
||||||
|
@ -48,7 +48,7 @@ class AdminSite(admin.AdminSite):
|
||||||
path('tools/statistics/<date:date>/',
|
path('tools/statistics/<date:date>/',
|
||||||
self.admin_view(StatisticsView.as_view()),
|
self.admin_view(StatisticsView.as_view()),
|
||||||
name='tools-stats'),
|
name='tools-stats'),
|
||||||
] + self.extra_urls
|
] + self.extra_urls + super().get_urls()
|
||||||
return urls
|
return urls
|
||||||
|
|
||||||
def get_tools(self):
|
def get_tools(self):
|
||||||
|
|
|
@ -128,7 +128,7 @@ class Command(BaseCommand):
|
||||||
def handle(self, path, *args, **options):
|
def handle(self, path, *args, **options):
|
||||||
# FIXME: absolute/relative path of sounds vs given path
|
# FIXME: absolute/relative path of sounds vs given path
|
||||||
if options.get('sound'):
|
if options.get('sound'):
|
||||||
sound = Sound.objects.filter(path__icontains=options.get('sound'))\
|
sound = Sound.objects.filter(file__icontains=options.get('sound'))\
|
||||||
.first()
|
.first()
|
||||||
else:
|
else:
|
||||||
path_, ext = os.path.splitext(path)
|
path_, ext = os.path.splitext(path)
|
||||||
|
|
|
@ -36,6 +36,7 @@ import mutagen
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
from watchdog.events import PatternMatchingEventHandler, FileModifiedEvent
|
from watchdog.events import PatternMatchingEventHandler, FileModifiedEvent
|
||||||
|
|
||||||
|
from django.conf import settings as conf
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
@ -64,12 +65,16 @@ class SoundFile:
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sound_path(self):
|
||||||
|
return self.path.replace(conf.MEDIA_ROOT + '/', '')
|
||||||
|
|
||||||
def sync(self, sound=None, program=None, deleted=False, **kwargs):
|
def sync(self, sound=None, program=None, deleted=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Update related sound model and save it.
|
Update related sound model and save it.
|
||||||
"""
|
"""
|
||||||
if deleted:
|
if deleted:
|
||||||
sound = Sound.objects.filter(path=self.path).first()
|
sound = Sound.objects.filter(file=self.path).first()
|
||||||
if sound:
|
if sound:
|
||||||
sound.type = sound.TYPE_REMOVED
|
sound.type = sound.TYPE_REMOVED
|
||||||
sound.check_on_file()
|
sound.check_on_file()
|
||||||
|
@ -78,13 +83,13 @@ class SoundFile:
|
||||||
|
|
||||||
# FIXME: sound.program as not null
|
# FIXME: sound.program as not null
|
||||||
program = kwargs['program'] = Program.get_from_path(self.path)
|
program = kwargs['program'] = Program.get_from_path(self.path)
|
||||||
sound, created = Sound.objects.get_or_create(path=self.path, defaults=kwargs) \
|
sound, created = Sound.objects.get_or_create(file=self.sound_path, defaults=kwargs) \
|
||||||
if not sound else (sound, False)
|
if not sound else (sound, False)
|
||||||
self.sound = sound
|
self.sound = sound
|
||||||
|
|
||||||
sound.program = program
|
sound.program = program
|
||||||
if created or sound.check_on_file():
|
if created or sound.check_on_file():
|
||||||
logger.info('sound is new or have been modified -> %s', self.path)
|
logger.info('sound is new or have been modified -> %s', self.sound_path)
|
||||||
self.read_path()
|
self.read_path()
|
||||||
sound.name = self.path_info.get('name')
|
sound.name = self.path_info.get('name')
|
||||||
|
|
||||||
|
@ -153,7 +158,7 @@ class SoundFile:
|
||||||
if not diffusion:
|
if not diffusion:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
logger.info('%s <--> %s', self.sound.path, str(diffusion.episode))
|
logger.info('%s <--> %s', self.sound.file.name, str(diffusion.episode))
|
||||||
self.sound.episode = diffusion.episode
|
self.sound.episode = diffusion.episode
|
||||||
return diffusion
|
return diffusion
|
||||||
|
|
||||||
|
@ -172,7 +177,7 @@ class SoundFile:
|
||||||
return
|
return
|
||||||
|
|
||||||
# import playlist
|
# import playlist
|
||||||
path = os.path.splitext(self.sound.path)[0] + '.csv'
|
path = os.path.splitext(self.sound.file.path)[0] + '.csv'
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
PlaylistImport(path, sound=sound).run()
|
PlaylistImport(path, sound=sound).run()
|
||||||
# use metadata
|
# use metadata
|
||||||
|
@ -227,7 +232,7 @@ class MonitorHandler(PatternMatchingEventHandler):
|
||||||
def on_moved(self, event):
|
def on_moved(self, event):
|
||||||
logger.info('sound moved: %s -> %s', event.src_path, event.dest_path)
|
logger.info('sound moved: %s -> %s', event.src_path, event.dest_path)
|
||||||
def moved(event, sound_kwargs):
|
def moved(event, sound_kwargs):
|
||||||
sound = Sound.objects.filter(path=event.src_path)
|
sound = Sound.objects.filter(file=event.src_path)
|
||||||
sound_file = SoundFile(event.dest_path) if not sound else sound
|
sound_file = SoundFile(event.dest_path) if not sound else sound
|
||||||
sound_file.sync(**sound_kwargs)
|
sound_file.sync(**sound_kwargs)
|
||||||
self.pool.submit(moved, event, self.sound_kwargs)
|
self.pool.submit(moved, event, self.sound_kwargs)
|
||||||
|
@ -268,7 +273,8 @@ class Command(BaseCommand):
|
||||||
program, settings.AIRCOX_SOUND_EXCERPTS_SUBDIR,
|
program, settings.AIRCOX_SOUND_EXCERPTS_SUBDIR,
|
||||||
type=Sound.TYPE_EXCERPT,
|
type=Sound.TYPE_EXCERPT,
|
||||||
)
|
)
|
||||||
dirs.append(os.path.join(program.path))
|
dirs.append(os.path.join(program.abspath))
|
||||||
|
return dirs
|
||||||
|
|
||||||
def scan_for_program(self, program, subdir, **sound_kwargs):
|
def scan_for_program(self, program, subdir, **sound_kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -279,7 +285,7 @@ class Command(BaseCommand):
|
||||||
if not program.ensure_dir(subdir):
|
if not program.ensure_dir(subdir):
|
||||||
return
|
return
|
||||||
|
|
||||||
subdir = os.path.join(program.path, subdir)
|
subdir = os.path.join(program.abspath, subdir)
|
||||||
sounds = []
|
sounds = []
|
||||||
|
|
||||||
# sounds in directory
|
# sounds in directory
|
||||||
|
@ -293,7 +299,7 @@ class Command(BaseCommand):
|
||||||
sounds.append(sound_file.sound.pk)
|
sounds.append(sound_file.sound.pk)
|
||||||
|
|
||||||
# sounds in db & unchecked
|
# sounds in db & unchecked
|
||||||
sounds = Sound.objects.filter(path__startswith=subdir). \
|
sounds = Sound.objects.filter(file__startswith=subdir). \
|
||||||
exclude(pk__in=sounds)
|
exclude(pk__in=sounds)
|
||||||
self.check_sounds(sounds, program=program)
|
self.check_sounds(sounds, program=program)
|
||||||
|
|
||||||
|
@ -302,7 +308,7 @@ class Command(BaseCommand):
|
||||||
# check files
|
# check files
|
||||||
for sound in qs:
|
for sound in qs:
|
||||||
if sound.check_on_file():
|
if sound.check_on_file():
|
||||||
SoundFile(sound.path).sync(sound=sound, **sync_kwargs)
|
SoundFile(sound.file.path).sync(sound=sound, **sync_kwargs)
|
||||||
|
|
||||||
def monitor(self):
|
def monitor(self):
|
||||||
""" Run in monitor mode """
|
""" Run in monitor mode """
|
||||||
|
|
|
@ -160,7 +160,7 @@ class Command (BaseCommand):
|
||||||
self.bad = []
|
self.bad = []
|
||||||
self.good = []
|
self.good = []
|
||||||
for sound in self.sounds:
|
for sound in self.sounds:
|
||||||
logger.info('analyse ' + sound.path)
|
logger.info('analyse ' + sound.file.name)
|
||||||
sound.analyse()
|
sound.analyse()
|
||||||
sound.check(attr, minmax[0], minmax[1])
|
sound.check(attr, minmax[0], minmax[1])
|
||||||
if sound.bad:
|
if sound.bad:
|
||||||
|
@ -171,6 +171,6 @@ class Command (BaseCommand):
|
||||||
# resume
|
# resume
|
||||||
if options.get('resume'):
|
if options.get('resume'):
|
||||||
for sound in self.good:
|
for sound in self.good:
|
||||||
logger.info('\033[92m+ %s\033[0m', sound.path)
|
logger.info('\033[92m+ %s\033[0m', sound.file.name)
|
||||||
for sound in self.bad:
|
for sound in self.bad:
|
||||||
logger.info('\033[91m+ %s\033[0m', sound.path)
|
logger.info('\033[91m+ %s\033[0m', sound.file.name)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
from django.conf import settings as conf
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
|
@ -73,14 +74,17 @@ class Program(Page):
|
||||||
self.slug.replace('-', '_'))
|
self.slug.replace('-', '_'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def archives_path(self):
|
def abspath(self):
|
||||||
return os.path.join(self.path, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR)
|
""" Return absolute path to program's dir """
|
||||||
|
return os.path.join(conf.MEDIA_ROOT, self.path)
|
||||||
|
|
||||||
@property
|
def archives_path(self, abs=False):
|
||||||
def excerpts_path(self):
|
return os.path.join(abs and self.abspath or self.path,
|
||||||
return os.path.join(
|
settings.AIRCOX_SOUND_ARCHIVES_SUBDIR)
|
||||||
self.path, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR
|
|
||||||
)
|
def excerpts_path(self, abs=False):
|
||||||
|
return os.path.join(abs and self.abspath or self.path,
|
||||||
|
settings.AIRCOX_SOUND_ARCHIVES_SUBDIR)
|
||||||
|
|
||||||
def __init__(self, *kargs, **kwargs):
|
def __init__(self, *kargs, **kwargs):
|
||||||
super().__init__(*kargs, **kwargs)
|
super().__init__(*kargs, **kwargs)
|
||||||
|
@ -94,6 +98,8 @@ class Program(Page):
|
||||||
Return a Program from the given path. We assume the path has been
|
Return a Program from the given path. We assume the path has been
|
||||||
given in a previous time by this model (Program.path getter).
|
given in a previous time by this model (Program.path getter).
|
||||||
"""
|
"""
|
||||||
|
if path.startswith(conf.MEDIA_ROOT):
|
||||||
|
path = path.replace(conf.MEDIA_ROOT + '/', '')
|
||||||
path = path.replace(settings.AIRCOX_PROGRAMS_DIR, '')
|
path = path.replace(settings.AIRCOX_PROGRAMS_DIR, '')
|
||||||
|
|
||||||
while path[0] == '/':
|
while path[0] == '/':
|
||||||
|
@ -107,10 +113,9 @@ class Program(Page):
|
||||||
Make sur the program's dir exists (and optionally subdir). Return True
|
Make sur the program's dir exists (and optionally subdir). Return True
|
||||||
if the dir (or subdir) exists.
|
if the dir (or subdir) exists.
|
||||||
"""
|
"""
|
||||||
path = os.path.join(self.path, subdir) if subdir else \
|
path = os.path.join(self.abspath, subdir) if subdir else \
|
||||||
self.path
|
self.abspath
|
||||||
os.makedirs(path, exist_ok=True)
|
os.makedirs(path, exist_ok=True)
|
||||||
|
|
||||||
return os.path.exists(path)
|
return os.path.exists(path)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -127,14 +132,15 @@ class Program(Page):
|
||||||
|
|
||||||
# TODO: move in signals
|
# TODO: move in signals
|
||||||
path_ = getattr(self, '__initial_path', None)
|
path_ = getattr(self, '__initial_path', None)
|
||||||
|
abspath = os.path.join(conf.MEDIA_ROOT, path_)
|
||||||
if path_ is not None and path_ != self.path and \
|
if path_ is not None and path_ != self.path and \
|
||||||
os.path.exists(path_) and not os.path.exists(self.path):
|
os.path.exists(abspath) and not os.path.exists(self.abspath):
|
||||||
logger.info('program #%s\'s dir changed to %s - update it.',
|
logger.info('program #%s\'s dir changed to %s - update it.',
|
||||||
self.id, self.title)
|
self.id, self.title)
|
||||||
|
|
||||||
shutil.move(path_, self.path)
|
shutil.move(abspath, self.abspath)
|
||||||
Sound.objects.filter(path__startswith=path_) \
|
Sound.objects.filter(path__startswith=path_) \
|
||||||
.update(path=Concat('path', Substr(F('path'), len(path_))))
|
.update(file=Concat('file', Substr(F('file'), len(path_))))
|
||||||
|
|
||||||
|
|
||||||
class ProgramChildQuerySet(PageQuerySet):
|
class ProgramChildQuerySet(PageQuerySet):
|
||||||
|
|
|
@ -4,7 +4,8 @@ import os
|
||||||
|
|
||||||
from django.conf import settings as main_settings
|
from django.conf import settings as main_settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q, Value as V
|
||||||
|
from django.db.models.functions import Concat
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
@ -54,7 +55,9 @@ class SoundQuerySet(models.QuerySet):
|
||||||
self = self.archive()
|
self = self.archive()
|
||||||
if order_by:
|
if order_by:
|
||||||
self = self.order_by('path')
|
self = self.order_by('path')
|
||||||
return self.filter(path__isnull=False).values_list('path', flat=True)
|
return self.filter(file__isnull=False) \
|
||||||
|
.annotate(file_path=Concat(V(conf.MEDIA_ROOT), 'file')) \
|
||||||
|
.values_list('file_path', flat=True)
|
||||||
|
|
||||||
def search(self, query):
|
def search(self, query):
|
||||||
return self.filter(
|
return self.filter(
|
||||||
|
@ -94,21 +97,15 @@ class Sound(models.Model):
|
||||||
position = models.PositiveSmallIntegerField(
|
position = models.PositiveSmallIntegerField(
|
||||||
_('order'), default=0, help_text=_('position in the playlist'),
|
_('order'), default=0, help_text=_('position in the playlist'),
|
||||||
)
|
)
|
||||||
# FIXME: url() does not use the same directory than here
|
|
||||||
# should we use FileField for more reliability?
|
def _upload_to(self, filename):
|
||||||
path = models.FilePathField(
|
subdir = AIRCOX_SOUND_ARCHIVES_SUBDIR if self.type == self.TYPE_ARCHIVE else \
|
||||||
_('file'),
|
AIRCOX_SOUND_EXCERPTS_SUBDIR
|
||||||
path=settings.AIRCOX_PROGRAMS_DIR,
|
return os.path.join(o.program.path, subdir, filename)
|
||||||
match=r'(' + '|'.join(settings.AIRCOX_SOUND_FILE_EXT)
|
|
||||||
.replace('.', r'\.') + ')$',
|
file = models.FileField(
|
||||||
recursive=True, max_length=255,
|
_('file'), upload_to=_upload_to
|
||||||
blank=True, null=True, unique=True,
|
|
||||||
)
|
)
|
||||||
#embed = models.TextField(
|
|
||||||
# _('embed'),
|
|
||||||
# blank=True, null=True,
|
|
||||||
# help_text=_('HTML code to embed a sound from an external plateform'),
|
|
||||||
#)
|
|
||||||
duration = models.TimeField(
|
duration = models.TimeField(
|
||||||
_('duration'),
|
_('duration'),
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
|
@ -134,8 +131,12 @@ class Sound(models.Model):
|
||||||
verbose_name = _('Sound')
|
verbose_name = _('Sound')
|
||||||
verbose_name_plural = _('Sounds')
|
verbose_name_plural = _('Sounds')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self.file and self.file.url
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '/'.join(self.path.split('/')[-3:])
|
return '/'.join(self.file.path.split('/')[-3:])
|
||||||
|
|
||||||
def save(self, check=True, *args, **kwargs):
|
def save(self, check=True, *args, **kwargs):
|
||||||
if self.episode is not None and self.program is None:
|
if self.episode is not None and self.program is None:
|
||||||
|
@ -145,17 +146,12 @@ class Sound(models.Model):
|
||||||
self.__check_name()
|
self.__check_name()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def url(self):
|
|
||||||
""" Return an url to the file. """
|
|
||||||
path = self.path.replace(main_settings.MEDIA_ROOT, '', 1)
|
|
||||||
return (main_settings.MEDIA_URL + path).replace('//','/')
|
|
||||||
|
|
||||||
# TODO: rename get_file_mtime(self)
|
# TODO: rename get_file_mtime(self)
|
||||||
def get_mtime(self):
|
def get_mtime(self):
|
||||||
"""
|
"""
|
||||||
Get the last modification date from file
|
Get the last modification date from file
|
||||||
"""
|
"""
|
||||||
mtime = os.stat(self.path).st_mtime
|
mtime = os.stat(self.file.path).st_mtime
|
||||||
mtime = tz.datetime.fromtimestamp(mtime)
|
mtime = tz.datetime.fromtimestamp(mtime)
|
||||||
mtime = mtime.replace(microsecond=0)
|
mtime = mtime.replace(microsecond=0)
|
||||||
return tz.make_aware(mtime, tz.get_current_timezone())
|
return tz.make_aware(mtime, tz.get_current_timezone())
|
||||||
|
@ -163,7 +159,7 @@ class Sound(models.Model):
|
||||||
def file_exists(self):
|
def file_exists(self):
|
||||||
""" Return true if the file still exists. """
|
""" Return true if the file still exists. """
|
||||||
|
|
||||||
return os.path.exists(self.path)
|
return os.path.exists(self.file.path)
|
||||||
|
|
||||||
def check_on_file(self):
|
def check_on_file(self):
|
||||||
"""
|
"""
|
||||||
|
@ -173,7 +169,7 @@ class Sound(models.Model):
|
||||||
if not self.file_exists():
|
if not self.file_exists():
|
||||||
if self.type == self.TYPE_REMOVED:
|
if self.type == self.TYPE_REMOVED:
|
||||||
return
|
return
|
||||||
logger.info('sound %s: has been removed', self.path)
|
logger.info('sound %s: has been removed', self.file.name)
|
||||||
self.type = self.TYPE_REMOVED
|
self.type = self.TYPE_REMOVED
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -183,7 +179,7 @@ class Sound(models.Model):
|
||||||
if self.type == self.TYPE_REMOVED and self.program:
|
if self.type == self.TYPE_REMOVED and self.program:
|
||||||
changed = True
|
changed = True
|
||||||
self.type = self.TYPE_ARCHIVE \
|
self.type = self.TYPE_ARCHIVE \
|
||||||
if self.path.startswith(self.program.archives_path) else \
|
if self.file.path.startswith(self.program.archives_path) else \
|
||||||
self.TYPE_EXCERPT
|
self.TYPE_EXCERPT
|
||||||
|
|
||||||
# check mtime -> reset quality if changed (assume file changed)
|
# check mtime -> reset quality if changed (assume file changed)
|
||||||
|
@ -193,15 +189,15 @@ class Sound(models.Model):
|
||||||
self.mtime = mtime
|
self.mtime = mtime
|
||||||
self.is_good_quality = None
|
self.is_good_quality = None
|
||||||
logger.info('sound %s: m_time has changed. Reset quality info',
|
logger.info('sound %s: m_time has changed. Reset quality info',
|
||||||
self.path)
|
self.file.name)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
def __check_name(self):
|
def __check_name(self):
|
||||||
if not self.name and self.path:
|
if not self.name and self.file and self.file.path:
|
||||||
# FIXME: later, remove date?
|
# FIXME: later, remove date?
|
||||||
self.name = os.path.basename(self.path)
|
self.name = os.path.basename(self.file.name)
|
||||||
self.name = os.path.splitext(self.name)[0]
|
self.name = os.path.splitext(self.name)[0]
|
||||||
self.name = self.name.replace('_', ' ')
|
self.name = self.name.replace('_', ' ')
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,6 @@ class LogInfoSerializer(serializers.Serializer):
|
||||||
|
|
||||||
class SoundSerializer(serializers.ModelSerializer):
|
class SoundSerializer(serializers.ModelSerializer):
|
||||||
# serializers.HyperlinkedIdentityField(view_name='sound', format='html')
|
# serializers.HyperlinkedIdentityField(view_name='sound', format='html')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Sound
|
model = Sound
|
||||||
fields = ['pk', 'name', 'program', 'episode', 'type',
|
fields = ['pk', 'name', 'program', 'episode', 'type',
|
||||||
|
@ -64,7 +63,7 @@ class SoundSerializer(serializers.ModelSerializer):
|
||||||
def get_field_names(self, *args):
|
def get_field_names(self, *args):
|
||||||
names = super().get_field_names(*args)
|
names = super().get_field_names(*args)
|
||||||
if 'request' in self.context and self.context['request'].user.is_staff:
|
if 'request' in self.context and self.context['request'].user.is_staff:
|
||||||
names.append('path')
|
names.append('file')
|
||||||
return names
|
return names
|
||||||
|
|
||||||
class PodcastSerializer(serializers.ModelSerializer):
|
class PodcastSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -87,8 +87,7 @@ ensure('AIRCOX_DEFAULT_USER_GROUPS', {
|
||||||
|
|
||||||
# Directory for the programs data
|
# Directory for the programs data
|
||||||
# TODO: rename to PROGRAMS_ROOT
|
# TODO: rename to PROGRAMS_ROOT
|
||||||
ensure('AIRCOX_PROGRAMS_DIR',
|
ensure('AIRCOX_PROGRAMS_DIR', 'programs')
|
||||||
os.path.join(settings.MEDIA_ROOT, 'programs'))
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
|
@ -152,8 +151,3 @@ ensure('AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER', ';')
|
||||||
ensure('AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE', '"')
|
ensure('AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE', '"')
|
||||||
|
|
||||||
|
|
||||||
if settings.MEDIA_ROOT not in AIRCOX_PROGRAMS_DIR:
|
|
||||||
# PROGRAMS_DIR must be in MEDIA_ROOT for easy files url resolution
|
|
||||||
# later should this restriction disappear.
|
|
||||||
raise ValueError("settings: AIRCOX_PROGRAMS_DIR must be in MEDIA_ROOT")
|
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,7 +8,7 @@ List item for a podcast.
|
||||||
{% if object.embed %}
|
{% if object.embed %}
|
||||||
{{ object.embed|safe }}
|
{{ object.embed|safe }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<audio src="{{ object.url }}" controls>
|
<audio src="{{ object.file.url }}" controls>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
<a-sound-item :data="{{ object|json }}" :player="player"
|
<a-sound-item :data="{{ object|json }}" :player="player"
|
||||||
|
|
|
@ -187,5 +187,5 @@ class QueueSourceViewSet(SourceViewSet):
|
||||||
sound = get_object_or_404(self.get_sound_queryset(),
|
sound = get_object_or_404(self.get_sound_queryset(),
|
||||||
pk=request.data['sound_id'])
|
pk=request.data['sound_id'])
|
||||||
return self._run(
|
return self._run(
|
||||||
pk, lambda s: s.push(sound.path) if sound.path else None)
|
pk, lambda s: s.push(sound.file.path) if sound.file.path else None)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ const App = {
|
||||||
|
|
||||||
export const PlayerApp = {
|
export const PlayerApp = {
|
||||||
el: '#player',
|
el: '#player',
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
components: {...components},
|
components: {...components},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ export default {
|
||||||
|
|
||||||
/// Push items to playlist (by name)
|
/// Push items to playlist (by name)
|
||||||
push(playlist, ...items) {
|
push(playlist, ...items) {
|
||||||
return this.$refs[playlist].push(...items);
|
return this.sets[playlist].push(...items);
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Push and play items
|
/// Push and play items
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
:key="index">
|
:key="index">
|
||||||
<a :class="index == selectedIndex ? 'is-active' : ''">
|
<a :class="index == selectedIndex ? 'is-active' : ''">
|
||||||
<ASoundItem
|
<ASoundItem
|
||||||
:data="item" :index="index" :player="player" :set="set"
|
:data="item" :index="index" :set="set" :player="player_"
|
||||||
@togglePlay="togglePlay(index)"
|
@togglePlay="togglePlay(index)"
|
||||||
:actions="actions">
|
:actions="actions">
|
||||||
<template v-slot:actions="{}">
|
<template v-slot:actions="{}">
|
||||||
|
@ -38,7 +38,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
self() { return this; }
|
self() { return this; },
|
||||||
|
player_() { return this.player || window.aircox.player },
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -50,8 +51,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
togglePlay(index) {
|
togglePlay(index) {
|
||||||
if(this.player.isPlaying(this.set.get(index)))
|
if(this.player_.isPlaying(this.set.get(index)))
|
||||||
this.player.pause();
|
this.player_.pause();
|
||||||
else
|
else
|
||||||
this.select(index)
|
this.select(index)
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,6 +35,11 @@ window.aircox = {
|
||||||
init(props=null, {config=null, builder=null, initBuilder=true,
|
init(props=null, {config=null, builder=null, initBuilder=true,
|
||||||
initPlayer=true, hotReload=false}={})
|
initPlayer=true, hotReload=false}={})
|
||||||
{
|
{
|
||||||
|
if(initPlayer) {
|
||||||
|
let playerBuilder = this.playerBuilder
|
||||||
|
playerBuilder.mount()
|
||||||
|
}
|
||||||
|
|
||||||
if(initBuilder) {
|
if(initBuilder) {
|
||||||
builder = builder || this.builder
|
builder = builder || this.builder
|
||||||
this.builder = builder
|
this.builder = builder
|
||||||
|
@ -46,11 +51,6 @@ window.aircox = {
|
||||||
if(hotReload)
|
if(hotReload)
|
||||||
builder.enableHotReload(hotReload)
|
builder.enableHotReload(hotReload)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(initPlayer) {
|
|
||||||
let playerBuilder = this.playerBuilder
|
|
||||||
playerBuilder.mount()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,19 @@
|
||||||
asgiref==3.2.10
|
Django~=3.0
|
||||||
bleach>=3.3.0
|
djangorestframework~=3.13
|
||||||
Django==3.1.6
|
django-model-utils>=4.2
|
||||||
django-admin-sortable2==0.7.7
|
django-filter~=21.1
|
||||||
django-ckeditor==6.0.0
|
|
||||||
django-content-editor==3.0.6
|
django-filer~=2.1
|
||||||
django-filer==2.0.2
|
django-honeypot~=1.0
|
||||||
django-filter==2.3.0
|
django-taggit~=2.1
|
||||||
django-honeypot==0.9.0
|
django-admin-sortable2~=1.0
|
||||||
django-js-asset==1.2.2
|
django-ckeditor~=6.2
|
||||||
django-model-utils==4.0.0
|
bleach~=4.1
|
||||||
django-mptt==0.11.0
|
easy-thumbnails~=2.8
|
||||||
django-polymorphic==3.0.0
|
tzlocal~=4.1
|
||||||
django-taggit==1.3.0
|
|
||||||
djangorestframework==3.11.2
|
mutagen~=1.45
|
||||||
easy-thumbnails==2.7
|
Pillow~=9.0
|
||||||
gunicorn==20.0.4
|
psutil~=5.9
|
||||||
mutagen==1.45.1
|
|
||||||
packaging==20.4
|
|
||||||
pathtools==0.1.2
|
|
||||||
Pillow==8.1.1
|
|
||||||
psutil==5.7.2
|
|
||||||
pyparsing==2.4.7
|
|
||||||
pytz==2020.1
|
|
||||||
PyYAML==5.4
|
PyYAML==5.4
|
||||||
six==1.14.0
|
watchdog~=2.1
|
||||||
sqlparse==0.3.1
|
|
||||||
tzlocal==2.1
|
|
||||||
Unidecode==1.1.1
|
|
||||||
watchdog==0.10.3
|
|
||||||
webencodings==0.5.1
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user