WIP - Sound.file instead of Sound.path; fix issues with player; program.path is now relative
This commit is contained in:
		@ -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
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user