forked from rc/aircox
add track to sound when scanning, using file's metadata (add mutagen as dep)
This commit is contained in:
parent
aa1c21a8c8
commit
0141d5174d
|
@ -116,21 +116,30 @@ class SoundInfo:
|
||||||
self.sound = sound
|
self.sound = sound
|
||||||
return sound
|
return sound
|
||||||
|
|
||||||
def find_playlist(self, sound):
|
def find_playlist(self, sound, use_default = True):
|
||||||
"""
|
"""
|
||||||
Find a playlist file corresponding to the sound path
|
Find a playlist file corresponding to the sound path, such as:
|
||||||
|
my_sound.ogg => my_sound.csv
|
||||||
|
|
||||||
|
If use_default is True and there is no playlist find found,
|
||||||
|
use sound file's metadata.
|
||||||
"""
|
"""
|
||||||
|
if sound.tracks.count():
|
||||||
|
return
|
||||||
|
|
||||||
import aircox.management.commands.import_playlist \
|
import aircox.management.commands.import_playlist \
|
||||||
as import_playlist
|
as import_playlist
|
||||||
|
|
||||||
|
# no playlist, try to retrieve metadata
|
||||||
path = os.path.splitext(self.sound.path)[0] + '.csv'
|
path = os.path.splitext(self.sound.path)[0] + '.csv'
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
|
if use_default:
|
||||||
|
track = sound.file_metadata()
|
||||||
|
if track:
|
||||||
|
track.save()
|
||||||
return
|
return
|
||||||
|
|
||||||
old = Track.objects.get_for(object = sound)
|
# else, import
|
||||||
if old:
|
|
||||||
return
|
|
||||||
|
|
||||||
import_playlist.Importer(sound, path, save=True)
|
import_playlist.Importer(sound, path, save=True)
|
||||||
|
|
||||||
def find_diffusion(self, program, save = True):
|
def find_diffusion(self, program, save = True):
|
||||||
|
@ -326,8 +335,10 @@ class Command(BaseCommand):
|
||||||
if check:
|
if check:
|
||||||
self.check_sounds(sounds)
|
self.check_sounds(sounds)
|
||||||
|
|
||||||
files = [ sound.path for sound in sounds
|
files = [
|
||||||
if os.path.exists(sound.path) ]
|
sound.path for sound in sounds
|
||||||
|
if os.path.exists(sound.path) and sound.good_quality is None
|
||||||
|
]
|
||||||
|
|
||||||
# check quality
|
# check quality
|
||||||
logger.info('quality check...',)
|
logger.info('quality check...',)
|
||||||
|
|
151
aircox/models.py
151
aircox/models.py
|
@ -10,7 +10,7 @@ from django.template.defaultfilters import slugify
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.conf import settings as main_settings
|
from django.conf import settings as main_settings
|
||||||
|
|
||||||
|
@ -115,6 +115,54 @@ class Nameable(models.Model):
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Small common models
|
||||||
|
#
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
# 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,
|
||||||
|
)
|
||||||
|
tags = TaggableManager(
|
||||||
|
verbose_name=_('tags'),
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
info = models.CharField(
|
||||||
|
_('information'),
|
||||||
|
max_length = 128,
|
||||||
|
blank = True, null = True,
|
||||||
|
help_text=_('additional informations about this track, such as '
|
||||||
|
'the version, if is it a remix, features, etc.'),
|
||||||
|
)
|
||||||
|
position = models.SmallIntegerField(
|
||||||
|
default = 0,
|
||||||
|
help_text=_('position in the playlist'),
|
||||||
|
)
|
||||||
|
in_seconds = models.BooleanField(
|
||||||
|
_('in seconds'),
|
||||||
|
default = False,
|
||||||
|
help_text=_('position in the playlist is expressed in seconds')
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{self.artist} -- {self.title}'.format(self=self)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Track')
|
||||||
|
verbose_name_plural = _('Tracks')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Station related classes
|
# Station related classes
|
||||||
#
|
#
|
||||||
|
@ -208,8 +256,20 @@ class Station(Nameable):
|
||||||
logs.filter(date__gt = diff.end, date__lt = diff_.start) \
|
logs.filter(date__gt = diff.end, date__lt = diff_.start) \
|
||||||
if diff_ else \
|
if diff_ else \
|
||||||
logs.filter(date__gt = diff.end)
|
logs.filter(date__gt = diff.end)
|
||||||
print(diff.end, *[str(log.date > diff.end) + " " + str(log.date) for log in logs])
|
|
||||||
|
|
||||||
|
# a log can be started before the end of the diffusion and
|
||||||
|
# still is running => need to add it to the list and change
|
||||||
|
# the start date
|
||||||
|
partial_log = logs.filter(
|
||||||
|
date__gt = diff.start, date__lt = diff.end
|
||||||
|
).last()
|
||||||
|
if partial_log:
|
||||||
|
next_log = logs.filter(pk__gt = partial_log.pk).first()
|
||||||
|
if not next_log or next_log.date > diff.date:
|
||||||
|
partial_log.date = diff.end
|
||||||
|
logs_ = [partial_log] + list(logs_[:count])
|
||||||
|
|
||||||
|
# append to list
|
||||||
diff_ = diff
|
diff_ = diff
|
||||||
items.extend(logs_)
|
items.extend(logs_)
|
||||||
items.append(diff)
|
items.append(diff)
|
||||||
|
@ -252,6 +312,7 @@ class Station(Nameable):
|
||||||
logs = Log.objects.get_for(model = Track) \
|
logs = Log.objects.get_for(model = Track) \
|
||||||
.filter(station = self) \
|
.filter(station = self) \
|
||||||
.order_by('-date')
|
.order_by('-date')
|
||||||
|
|
||||||
if date:
|
if date:
|
||||||
logs = logs.filter(date__contains = date)
|
logs = logs.filter(date__contains = date)
|
||||||
diffs = Diffusion.objects.get_at(date)
|
diffs = Diffusion.objects.get_at(date)
|
||||||
|
@ -730,6 +791,8 @@ class Diffusion(models.Model):
|
||||||
start = models.DateTimeField( _('start of the diffusion') )
|
start = models.DateTimeField( _('start of the diffusion') )
|
||||||
end = models.DateTimeField( _('end of the diffusion') )
|
end = models.DateTimeField( _('end of the diffusion') )
|
||||||
|
|
||||||
|
tracks = GenericRelation(Track, 'related_id', 'related_type')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def duration(self):
|
def duration(self):
|
||||||
return self.end - self.start
|
return self.end - self.start
|
||||||
|
@ -855,10 +918,10 @@ class Sound(Nameable):
|
||||||
blank = True, null = True,
|
blank = True, null = True,
|
||||||
help_text = _('last modification date and time'),
|
help_text = _('last modification date and time'),
|
||||||
)
|
)
|
||||||
good_quality = models.BooleanField(
|
good_quality = models.NullBooleanField(
|
||||||
_('good quality'),
|
_('good quality'),
|
||||||
default = False,
|
help_text = _('sound\'s quality is okay'),
|
||||||
help_text = _('sound\'s quality is okay')
|
blank = True, null = True
|
||||||
)
|
)
|
||||||
public = models.BooleanField(
|
public = models.BooleanField(
|
||||||
_('public'),
|
_('public'),
|
||||||
|
@ -866,6 +929,8 @@ class Sound(Nameable):
|
||||||
help_text = _('the sound is accessible to the public')
|
help_text = _('the sound is accessible to the public')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tracks = GenericRelation(Track, 'related_id', 'related_type')
|
||||||
|
|
||||||
def get_mtime(self):
|
def get_mtime(self):
|
||||||
"""
|
"""
|
||||||
Get the last modification date from file
|
Get the last modification date from file
|
||||||
|
@ -891,6 +956,36 @@ class Sound(Nameable):
|
||||||
"""
|
"""
|
||||||
return os.path.exists(self.path)
|
return os.path.exists(self.path)
|
||||||
|
|
||||||
|
def file_metadata(self):
|
||||||
|
"""
|
||||||
|
Get metadata from sound file and return a Track object if succeed,
|
||||||
|
else None.
|
||||||
|
"""
|
||||||
|
if not self.file_exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
import mutagen
|
||||||
|
meta = mutagen.File(self.path)
|
||||||
|
|
||||||
|
def get_meta(key, cast=str):
|
||||||
|
value = meta.get(key)
|
||||||
|
return cast(value[0]) if value else None
|
||||||
|
|
||||||
|
info = '{} ({})'.format(get_meta('album'), get_meta('year')) \
|
||||||
|
if 'album' and 'year' in meta else \
|
||||||
|
get_meta('album') \
|
||||||
|
if 'album' else \
|
||||||
|
('year' in meta) and get_meta('year') or ''
|
||||||
|
|
||||||
|
track = Track(
|
||||||
|
related = self,
|
||||||
|
title = get_meta('title') or self.name,
|
||||||
|
artist = get_meta('artist') or _('unknown'),
|
||||||
|
info = info,
|
||||||
|
position = get_meta('tracknumber', int) or 0,
|
||||||
|
)
|
||||||
|
return track
|
||||||
|
|
||||||
def check_on_file(self):
|
def check_on_file(self):
|
||||||
"""
|
"""
|
||||||
Check sound file info again'st self, and update informations if
|
Check sound file info again'st self, and update informations if
|
||||||
|
@ -915,7 +1010,7 @@ class Sound(Nameable):
|
||||||
mtime = self.get_mtime()
|
mtime = self.get_mtime()
|
||||||
if self.mtime != mtime:
|
if self.mtime != mtime:
|
||||||
self.mtime = mtime
|
self.mtime = mtime
|
||||||
self.good_quality = False
|
self.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.path)
|
||||||
return True
|
return True
|
||||||
|
@ -965,50 +1060,6 @@ class Sound(Nameable):
|
||||||
verbose_name_plural = _('Sounds')
|
verbose_name_plural = _('Sounds')
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
# 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,
|
|
||||||
)
|
|
||||||
tags = TaggableManager(
|
|
||||||
verbose_name=_('tags'),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
info = models.CharField(
|
|
||||||
_('information'),
|
|
||||||
max_length = 128,
|
|
||||||
blank = True, null = True,
|
|
||||||
help_text=_('additional informations about this track, such as '
|
|
||||||
'the version, if is it a remix, features, etc.'),
|
|
||||||
)
|
|
||||||
position = models.SmallIntegerField(
|
|
||||||
default = 0,
|
|
||||||
help_text=_('position in the playlist'),
|
|
||||||
)
|
|
||||||
in_seconds = models.BooleanField(
|
|
||||||
_('in seconds'),
|
|
||||||
default = False,
|
|
||||||
help_text=_('position in the playlist is expressed in seconds')
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{self.artist} -- {self.title}'.format(self=self)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Track')
|
|
||||||
verbose_name_plural = _('Tracks')
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Controls and audio output
|
# Controls and audio output
|
||||||
#
|
#
|
||||||
|
|
|
@ -40,7 +40,7 @@ def on_air(request):
|
||||||
else:
|
else:
|
||||||
station = stations.stations.first()
|
station = stations.stations.first()
|
||||||
|
|
||||||
last = station.on_air(count = 1)
|
last = station.on_air(count = 10)
|
||||||
if not last:
|
if not last:
|
||||||
return HttpResponse('')
|
return HttpResponse('')
|
||||||
|
|
||||||
|
|
|
@ -462,6 +462,7 @@ class DatedListBase(models.Model):
|
||||||
# context dict
|
# context dict
|
||||||
return {
|
return {
|
||||||
'nav_dates': {
|
'nav_dates': {
|
||||||
|
'today': today,
|
||||||
'date': date,
|
'date': date,
|
||||||
'next': next,
|
'next': next,
|
||||||
'prev': prev,
|
'prev': prev,
|
||||||
|
@ -935,12 +936,14 @@ class SectionLogsList(SectionItem):
|
||||||
print(log, type(log))
|
print(log, type(log))
|
||||||
if type(log) == aircox.models.Diffusion:
|
if type(log) == aircox.models.Diffusion:
|
||||||
return DiffusionPage.as_item(log)
|
return DiffusionPage.as_item(log)
|
||||||
|
|
||||||
|
related = log.related
|
||||||
return ListItem(
|
return ListItem(
|
||||||
title = '{artist} -- {title}'.format(
|
title = '{artist} -- {title}'.format(
|
||||||
artist = log.related.artist,
|
artist = related.artist,
|
||||||
title = log.related.title,
|
title = related.title,
|
||||||
),
|
),
|
||||||
summary = log.related.info,
|
summary = related.info,
|
||||||
date = log.date,
|
date = log.date,
|
||||||
info = '♫',
|
info = '♫',
|
||||||
css_class = 'track'
|
css_class = 'track'
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
<div class="list date_list">
|
<div class="list date_list">
|
||||||
{% if nav_dates %}
|
{% if nav_dates %}
|
||||||
<nav class="nav_dates">
|
<nav class="nav_dates">
|
||||||
|
<a href="?date={{ nav_dates.today|date:"Y-m-d" }}" title="{% trans "today" %}">○</a>
|
||||||
|
|
||||||
{% if nav_dates.prev %}
|
{% if nav_dates.prev %}
|
||||||
<a href="?date={{ nav_dates.prev|date:"Y-m-d" }}" title="{% trans "previous days" %}">◀</a>
|
<a href="?date={{ nav_dates.prev|date:"Y-m-d" }}" title="{% trans "previous days" %}">◀</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -11,7 +13,9 @@
|
||||||
{% for day in nav_dates.dates %}
|
{% for day in nav_dates.dates %}
|
||||||
<a onclick="select_tab(this, '.panel[data-date=\'{{day|date:"Y-m-d"}}\']');"
|
<a onclick="select_tab(this, '.panel[data-date=\'{{day|date:"Y-m-d"}}\']');"
|
||||||
{% if day == nav_dates.date %}selected{% endif %}
|
{% if day == nav_dates.date %}selected{% endif %}
|
||||||
class="tab {% if day == nav_dates.date %}today{% endif %}">
|
class="tab {% if day == nav_dates.date %}today{% endif %}"
|
||||||
|
title="{{ day|date:"l d F Y" }}"
|
||||||
|
>
|
||||||
{{ day|date:'D. d' }}
|
{{ day|date:'D. d' }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
Django>=1.9a1
|
Django>=1.10.3
|
||||||
django-taggit>=0.18.3
|
django-taggit>=0.18.3
|
||||||
|
mutagen=1.35.1
|
||||||
watchdog>=0.8.3
|
watchdog>=0.8.3
|
||||||
wagtail>=1.5.3
|
|
||||||
Pillow>=3.3.0
|
|
||||||
django-honeypot>=0.5.0
|
|
||||||
dateutils>=0.6.6
|
dateutils>=0.6.6
|
||||||
bleach>=1.4.3
|
bleach>=1.4.3
|
||||||
|
django-htmlmin>=0.10.0
|
||||||
|
wagtail>=1.5.3
|
||||||
|
django-overextend>=0.4.2
|
||||||
|
Pillow>=3.3.0
|
||||||
|
django-modelcluster=2.0
|
||||||
|
django-honeypot>=0.5.0
|
||||||
|
django-jet>=1.0.3
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user