playlist import -- fixes and integration into the sound monitor

This commit is contained in:
bkfox 2016-07-16 23:51:53 +02:00
parent 161af3fb1a
commit 32a30004d6
7 changed files with 75 additions and 70 deletions

View File

@ -166,5 +166,9 @@ class ScheduleAdmin(admin.ModelAdmin):
list_display = ['id', 'program_name', 'frequency', 'date', 'day', 'rerun']
list_editable = ['frequency', 'date']
admin.site.register(Track)
@admin.register(Track)
class TrackAdmin(admin.ModelAdmin):
list_display = ['id', 'title', 'artist', 'position', 'pos_in_secs', 'related']

View File

@ -2,20 +2,13 @@
Import one or more playlist for the given sound. Attach it to the sound
or to the related Diffusion if wanted.
We support different formats:
- plain text: a track per line, where columns are separated with a
'{settings.AIRCOX_IMPORT_PLAYLIST_PLAIN_DELIMITER}'.
The order of the elements is: {settings.AIRCOX_IMPORT_PLAYLIST_PLAIN_COLS}
- csv: CSV file where columns are separated with a
'{settings.AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER)}'. Text quote is
{settings.AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE}.
The order of the elements is: {settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS}
Playlists are in CSV format, where columns are separated with a
'{settings.AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER}'. Text quote is
{settings.AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE}.
The order of the elements is: {settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS}
If 'minutes' or 'seconds' are given, position will be expressed as timed
position, instead of position in playlist.
Base the format detection using file extension. If '.csv', uses CSV importer,
otherwise plain text one.
"""
import os
import csv
@ -23,52 +16,41 @@ import logging
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError
from django.contrib.contenttypes.models import ContentType
from aircox.programs.models import *
import aircox.programs.settings as settings
__doc__ = __doc__.format(settings)
__doc__ = __doc__.format(settings = settings)
logger = logging.getLogger('aircox.tools')
class Importer:
type = None
data = None
tracks = None
def __init__(self, related = None, path = None):
def __init__(self, related = None, path = None, save = False):
if path:
self.read(path)
if related:
self.make_playlist(related, True)
self.make_playlist(related, save)
def reset(self):
self.type = None
self.data = None
self.tracks = None
def read(self, path):
if not os.path.exists(path):
return True
with open(path, 'r') as file:
sp, *ext = os.path.splitext(path)[1]
if ext[0] and ext[0] == 'csv':
self.type = 'csv'
self.data = csv.reader(
self.data = list(csv.reader(
file,
delimiter = settings.AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER,
quotechar = settings.AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE,
)
else:
self.type = 'plain'
self.data = [
line.split(settings.AIRCOX_IMPORT_PLAYLIST_PLAIN_DELIMITER)
for line in file.readlines()
]
))
def __get(self, line, field, default = None):
maps = settings.AIRCOX_IMPORT_CSV_COLS
maps = settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS
if field not in maps:
return default
index = maps.index(field)
@ -79,26 +61,30 @@ class Importer:
Make a playlist from the read data, and return it. If save is
true, save it into the database
"""
maps = settings.AIRCOX_IMPORT_CSV_COLS if self.type == 'csv' else \
settings.AIRCOX_IMPORT_PLAIN_COLS
maps = settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS
tracks = []
pos_in_secs = ('minutes' or 'seconds') in maps
for index, line in enumerate(self.data):
if ('minutes' or 'seconds') in maps:
kwargs['pos_in_secs'] = True
kwargs['pos'] = int(self.__get(line, 'minutes', 0)) * 60 + \
int(self.__get(line, 'seconds', 0))
else:
kwargs['pos'] = index
position = \
int(self.__get(line, 'minutes', 0)) * 60 + \
int(self.__get(line, 'seconds', 0)) \
if pos_in_secs else index
kwargs['related'] = related
kwargs.update({
k: self.__get(line, k) for k in maps
if k not in ('minutes', 'seconds')
})
track, created = Track.objects.get_or_create(
related_type = ContentType.objects.get_for_model(related),
related_id = related.pk,
title = self.__get(line, 'title'),
artist = self.__get(line, 'artist'),
position = position,
)
track.pos_in_secs = pos_in_secs
track.info = self.__get(line, 'info')
tags = self.__get(line, 'tags')
if tags:
track.tags.add(*tags.split(','))
track = Track(**kwargs)
# FIXME: bulk_create?
if save:
track.save()
tracks.append(track)
@ -114,7 +100,7 @@ class Command (BaseCommand):
now = tz.datetime.today()
parser.add_argument(
'path', type=str,
'path', metavar='PATH', type=str,
help='path of the input playlist to read'
)
parser.add_argument(
@ -130,7 +116,9 @@ class Command (BaseCommand):
def handle (self, path, *args, **options):
# FIXME: absolute/relative path of sounds vs given path
if options.get('sound'):
related = Sound.objects.filter(path__icontains = path).first()
related = Sound.objects.filter(
path__icontains = options.get('sound')
).first()
else:
path, ext = os.path.splitext(options.get('path'))
related = Sound.objects.filter(path__icontains = path).first()
@ -143,12 +131,12 @@ class Command (BaseCommand):
if options.get('diffusion') and related.diffusion:
related = related.diffusion
importer = Importer(related = related, path = path)
importer = Importer(related = related, path = path, save = True)
for track in importer.tracks:
logger.log('imported track at {pos}: {name}, by '
logger.info('imported track at {pos}: {title}, by '
'{artist}'.format(
pos = track.pos,
name = track.name, artist = track.artist
pos = track.position,
title = track.title, artist = track.artist
)
)

View File

@ -101,7 +101,7 @@ class SoundInfo:
Get or create a sound using self info.
If the sound is created/modified, get its duration and update it
(if save is True, sync to DB).
(if save is True, sync to DB), and check for a playlist file.
"""
sound, created = Sound.objects.get_or_create(
path = self.path,
@ -116,6 +116,23 @@ class SoundInfo:
self.sound = sound
return sound
def find_playlist(self, sound):
"""
Find a playlist file corresponding to the sound path
"""
import aircox.programs.management.commands.import_playlist \
as import_playlist
path = os.path.splitext(self.sound.path)[0] + '.csv'
if not os.path.exists(path):
return
old = Tracks.get_for(object = sound).exclude(tracks_id)
if old:
return
import_playlist.Importer(sound, path, save=True)
def find_diffusion(self, program, save = True):
"""
For a given program, check if there is an initial diffusion
@ -229,7 +246,6 @@ class Command(BaseCommand):
'and react in consequence'
)
def handle(self, *args, **options):
if options.get('scan'):
self.scan()
@ -287,6 +303,7 @@ class Command(BaseCommand):
si = SoundInfo(path)
si.get_sound(sound_kwargs, True)
si.find_diffusion(program)
si.find_playlist(si.sound)
sounds.append(si.sound.pk)
# sounds in db & unchecked

View File

@ -723,7 +723,7 @@ class Diffusion(models.Model):
)
class Track(Nameable,Related):
class Track(Related):
"""
Track of a playlist of an object. The position can either be expressed
as the position in the playlist or as the moment in seconds it started.
@ -731,6 +731,10 @@ class Track(Nameable,Related):
# There are no nice solution for M2M relations ship (even without
# through) in django-admin. So we unfortunately need to make one-
# to-one relations and add a position argument
title = models.CharField (
_('title'),
max_length = 128,
)
artist = models.CharField(
_('artist'),
max_length = 128,
@ -757,7 +761,7 @@ class Track(Nameable,Related):
)
def __str__(self):
return ' '.join([self.artist, ':', self.name ])
return '{self.artist} -- {self.title}'.format(self=self)
class Meta:
verbose_name = _('Track')

View File

@ -46,18 +46,10 @@ ensure(
ensure('AIRCOX_SCHEDULED_STREAM', 0)
# Import playlist: columns for plain text files
ensure(
'AIRCOX_IMPORT_PLAYLIST_PLAIN_COLS',
('artist', 'title', 'tags', 'version')
)
# Import playlist: delimiter for plain text files
ensure('AIRCOX_IMPORT_PLAYLIST_PLAIN_DELIMITER', '--')
# Import playlist: columns for CSV file
ensure(
'AIRCOX_IMPORT_PLAYLIST_CSV_COLS',
('artist', 'title', 'minutes', 'seconds', 'tags', 'version')
('artist', 'title', 'minutes', 'seconds', 'tags', 'info')
)
# Import playlist: column delimiter of csv text files
ensure('AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER', ';')

View File

@ -9,7 +9,7 @@ import aircox.programs.models as programs
class TrackForm (forms.ModelForm):
class Meta:
model = programs.Track
fields = ['artist', 'name', 'tags', 'position']
fields = ['artist', 'title', 'tags', 'position']
widgets = {
# 'artist': al.TextWidget('TrackArtistAutocomplete'),
# 'name': al.TextWidget('TrackNameAutocomplete'),

View File

@ -231,7 +231,7 @@ class Playlist(sections.List):
def get_object_list(self):
tracks = programs.Track.get_for(object = self.object.related) \
.order_by('position')
return [ sections.ListItem(title=track.name, content=track.artist)
return [ sections.ListItem(title=track.title, content=track.artist)
for track in tracks ]