work on website; fix stuffs on aircox too

This commit is contained in:
bkfox
2019-07-22 01:37:25 +02:00
parent 08d1c7bfac
commit 3e432c42b0
35 changed files with 1089 additions and 416 deletions

View File

@ -16,14 +16,14 @@ logger = logging.getLogger('aircox.tools')
class Command (BaseCommand):
help= __doc__
help = __doc__
def add_arguments (self, parser):
parser.formatter_class=RawTextHelpFormatter
def add_arguments(self, parser):
parser.formatter_class = RawTextHelpFormatter
group = parser.add_argument_group('actions')
group.add_argument(
'-a', '--age', type=int,
default = settings.AIRCOX_LOGS_ARCHIVES_MIN_AGE,
default=settings.AIRCOX_LOGS_ARCHIVES_MIN_AGE,
help='minimal age in days of logs to archive. Default is '
'settings.AIRCOX_LOGS_ARCHIVES_MIN_AGE'
)
@ -36,22 +36,21 @@ class Command (BaseCommand):
help='keep logs in database instead of deleting them'
)
def handle (self, *args, age, force, keep, **options):
date = tz.now() - tz.timedelta(days = age)
def handle(self, *args, age, force, keep, **options):
date = tz.now() - tz.timedelta(days=age)
while True:
date = date.replace(
hour = 0, minute = 0, second = 0, microsecond = 0
hour=0, minute=0, second=0, microsecond=0
)
logger.info('archive log at date %s', date)
for station in Station.objects.all():
Log.objects.make_archive(
station, date, force = force, keep = keep
station, date, force=force, keep=keep
)
qs = Log.objects.filter(date__lt = date)
qs = Log.objects.filter(date__lt=date)
if not qs.exists():
break
date = qs.order_by('-date').first().date

View File

@ -15,6 +15,7 @@ planified before the (given) month.
- "check" will remove all diffusions that are unconfirmed and have been planified
from the (given) month and later.
"""
import time
import logging
from argparse import RawTextHelpFormatter
@ -25,53 +26,52 @@ from aircox.models import *
logger = logging.getLogger('aircox.tools')
import time
class Actions:
@classmethod
def update (cl, date, mode):
def update(cl, date, mode):
manual = (mode == 'manual')
count = [0, 0]
for schedule in Schedule.objects.filter(program__active = True) \
for schedule in Schedule.objects.filter(program__active=True) \
.order_by('initial'):
# in order to allow rerun links between diffusions, we save items
# by schedule;
items = schedule.diffusions_of_month(date, exclude_saved = True)
items = schedule.diffusions_of_month(date, exclude_saved=True)
count[0] += len(items)
# we can't bulk create because we need signal processing
for item in items:
conflicts = item.get_conflicts()
item.type = Diffusion.Type.unconfirmed \
if manual or conflicts.count() else \
Diffusion.Type.normal
item.save(no_check = True)
if manual or conflicts.count() else \
Diffusion.Type.normal
item.save(no_check=True)
if conflicts.count():
item.conflicts.set(conflicts.all())
logger.info('[update] schedule %s: %d new diffusions',
str(schedule), len(items),
)
str(schedule), len(items),
)
logger.info('[update] %d diffusions have been created, %s', count[0],
'do not forget manual approval' if manual else
'{} conflicts found'.format(count[1]))
'do not forget manual approval' if manual else
'{} conflicts found'.format(count[1]))
@staticmethod
def clean (date):
qs = Diffusion.objects.filter(type = Diffusion.Type.unconfirmed,
start__lt = date)
def clean(date):
qs = Diffusion.objects.filter(type=Diffusion.Type.unconfirmed,
start__lt=date)
logger.info('[clean] %d diffusions will be removed', qs.count())
qs.delete()
@staticmethod
def check(date):
qs = Diffusion.objects.filter(type = Diffusion.Type.unconfirmed,
start__gt = date)
qs = Diffusion.objects.filter(type=Diffusion.Type.unconfirmed,
start__gt=date)
items = []
for diffusion in qs:
schedules = Schedule.objects.filter(program = diffusion.program)
schedules = Schedule.objects.filter(program=diffusion.program)
for schedule in schedules:
if schedule.match(diffusion.start):
break
@ -80,14 +80,14 @@ class Actions:
logger.info('[check] %d diffusions will be removed', len(items))
if len(items):
Diffusion.objects.filter(id__in = items).delete()
Diffusion.objects.filter(id__in=items).delete()
class Command(BaseCommand):
help= __doc__
help = __doc__
def add_arguments (self, parser):
parser.formatter_class=RawTextHelpFormatter
def add_arguments(self, parser):
parser.formatter_class = RawTextHelpFormatter
now = tz.datetime.today()
group = parser.add_argument_group('action')
@ -130,23 +130,22 @@ class Command(BaseCommand):
'diffusions except those that conflicts with others'
)
def handle (self, *args, **options):
date = tz.datetime(year = options.get('year'),
month = options.get('month'),
day = 1)
def handle(self, *args, **options):
date = tz.datetime(year=options.get('year'),
month=options.get('month'),
day=1)
date = tz.make_aware(date)
if options.get('next_month'):
month = options.get('month')
date += tz.timedelta(days = 28)
date += tz.timedelta(days=28)
if date.month == month:
date += tz.timedelta(days = 28)
date += tz.timedelta(days=28)
date = date.replace(day = 1)
date = date.replace(day=1)
if options.get('update'):
Actions.update(date, mode = options.get('mode'))
Actions.update(date, mode=options.get('mode'))
if options.get('clean'):
Actions.clean(date)
if options.get('check'):
Actions.check(date)

View File

@ -20,7 +20,7 @@ from django.contrib.contenttypes.models import ContentType
from aircox.models import *
import aircox.settings as settings
__doc__ = __doc__.format(settings = settings)
__doc__ = __doc__.format(settings=settings)
logger = logging.getLogger('aircox.tools')
@ -78,8 +78,8 @@ class Importer:
return
try:
timestamp = int(line.get('minutes') or 0) * 60 + \
int(line.get('seconds') or 0) \
if has_timestamp else None
int(line.get('seconds') or 0) \
if has_timestamp else None
track, created = Track.objects.get_or_create(
title=line.get('title'),
@ -88,6 +88,7 @@ class Importer:
**self.track_kwargs
)
track.timestamp = timestamp
print('track', track, timestamp)
track.info = line.get('info')
tags = line.get('tags')
if tags:
@ -96,7 +97,7 @@ class Importer:
logger.warning(
'an error occured for track {index}, it may not '
'have been saved: {err}'
.format(index = index, err=err)
.format(index=index, err=err)
)
continue
@ -107,10 +108,10 @@ class Importer:
class Command (BaseCommand):
help= __doc__
help = __doc__
def add_arguments(self, parser):
parser.formatter_class=RawTextHelpFormatter
parser.formatter_class = RawTextHelpFormatter
parser.add_argument(
'path', metavar='PATH', type=str,
help='path of the input playlist to read'
@ -125,7 +126,7 @@ class Command (BaseCommand):
help='try to get the diffusion relative to the sound if it exists'
)
def handle (self, path, *args, **options):
def handle(self, path, *args, **options):
# FIXME: absolute/relative path of sounds vs given path
if options.get('sound'):
sound = Sound.objects.filter(
@ -136,7 +137,7 @@ class Command (BaseCommand):
sound = Sound.objects.filter(path__icontains=path_).first()
if not sound:
logger.error('no sound found in the database for the path ' \
logger.error('no sound found in the database for the path '
'{path}'.format(path=path))
return
@ -148,4 +149,3 @@ class Command (BaseCommand):
logger.info('track #{pos} imported: {title}, by {artist}'.format(
pos=track.position, title=track.title, artist=track.artist
))

View File

@ -11,6 +11,7 @@ from django.core.management.base import BaseCommand, CommandError
logger = logging.getLogger('aircox.tools')
class Stats:
attributes = [
'DC offset', 'Min level', 'Max level',
@ -18,7 +19,7 @@ class Stats:
'RMS Tr dB', 'Flat factor', 'Length s',
]
def __init__ (self, path, **kwargs):
def __init__(self, path, **kwargs):
"""
If path is given, call analyse with path and kwargs
"""
@ -26,10 +27,10 @@ class Stats:
if path:
self.analyse(path, **kwargs)
def get (self, attr):
def get(self, attr):
return self.values.get(attr)
def parse (self, output):
def parse(self, output):
for attr in Stats.attributes:
value = re.search(attr + r'\s+(?P<value>\S+)', output)
value = value and value.groupdict()
@ -41,14 +42,14 @@ class Stats:
self.values[attr] = value
self.values['length'] = self.values['Length s']
def analyse (self, path, at = None, length = None):
def analyse(self, path, at=None, length=None):
"""
If at and length are given use them as excerpt to analyse.
"""
args = ['sox', path, '-n']
if at is not None and length is not None:
args += ['trim', str(at), str(length) ]
args += ['trim', str(at), str(length)]
args.append('stats')
@ -66,17 +67,17 @@ class Sound:
bad = None # list of bad samples
good = None # list of good samples
def __init__ (self, path, sample_length = None):
def __init__(self, path, sample_length=None):
self.path = path
self.sample_length = sample_length if sample_length is not None \
else self.sample_length
else self.sample_length
def get_file_stats (self):
def get_file_stats(self):
return self.stats and self.stats[0]
def analyse (self):
def analyse(self):
logger.info('complete file analysis')
self.stats = [ Stats(self.path) ]
self.stats = [Stats(self.path)]
position = 0
length = self.stats[0].get('length')
@ -85,21 +86,22 @@ class Sound:
logger.info('start samples analysis...')
while position < length:
stats = Stats(self.path, at = position, length = self.sample_length)
stats = Stats(self.path, at=position, length=self.sample_length)
self.stats.append(stats)
position += self.sample_length
def check (self, name, min_val, max_val):
self.good = [ index for index, stats in enumerate(self.stats)
if min_val <= stats.get(name) <= max_val ]
self.bad = [ index for index, stats in enumerate(self.stats)
if index not in self.good ]
def check(self, name, min_val, max_val):
self.good = [index for index, stats in enumerate(self.stats)
if min_val <= stats.get(name) <= max_val]
self.bad = [index for index, stats in enumerate(self.stats)
if index not in self.good]
self.resume()
def resume (self):
view = lambda array: [
def resume(self):
def view(array): return [
'file' if index is 0 else
'sample {} (at {} seconds)'.format(index, (index-1) * self.sample_length)
'sample {} (at {} seconds)'.format(
index, (index-1) * self.sample_length)
for index in array
]
@ -110,12 +112,13 @@ class Sound:
logger.info(self.path + ' -> bad: \033[91m%s\033[0m',
', '.join(view(self.bad)))
class Command (BaseCommand):
help = __doc__
sounds = None
def add_arguments (self, parser):
parser.formatter_class=RawTextHelpFormatter
def add_arguments(self, parser):
parser.formatter_class = RawTextHelpFormatter
parser.add_argument(
'files', metavar='FILE', type=str, nargs='+',
@ -128,12 +131,12 @@ class Command (BaseCommand):
)
parser.add_argument(
'-a', '--attribute', type=str,
help='attribute name to use to check, that can be:\n' + \
', '.join([ '"{}"'.format(attr) for attr in Stats.attributes ])
help='attribute name to use to check, that can be:\n' +
', '.join(['"{}"'.format(attr) for attr in Stats.attributes])
)
parser.add_argument(
'-r', '--range', type=float, nargs=2,
help='range of minimal and maximal accepted value such as: ' \
help='range of minimal and maximal accepted value such as: '
'--range min max'
)
parser.add_argument(
@ -141,7 +144,7 @@ class Command (BaseCommand):
help='print a resume of good and bad files'
)
def handle (self, *args, **options):
def handle(self, *args, **options):
# parameters
minmax = options.get('range')
if not minmax:
@ -152,8 +155,8 @@ class Command (BaseCommand):
raise CommandError('no attribute specified')
# sound analyse and checks
self.sounds = [ Sound(path, options.get('sample_length'))
for path in options.get('files') ]
self.sounds = [Sound(path, options.get('sample_length'))
for path in options.get('files')]
self.bad = []
self.good = []
for sound in self.sounds:
@ -171,4 +174,3 @@ class Command (BaseCommand):
logger.info('\033[92m+ %s\033[0m', sound.path)
for sound in self.bad:
logger.info('\033[91m+ %s\033[0m', sound.path)

View File

@ -6,6 +6,7 @@ used to:
- cancels Diffusions that have an archive but could not have been played;
- run Liquidsoap
"""
import tzlocal
import time
import re
@ -28,7 +29,6 @@ tz.activate(pytz.UTC)
# FIXME liquidsoap does not manage timezones -- we have to convert
# 'on_air' metadata we get from it into utc one in order to work
# correctly.
import tzlocal
local_tz = tzlocal.get_localzone()
@ -118,10 +118,12 @@ class Monitor:
self.handle()
def log(self, date=None, **kwargs):
"""
Create a log using **kwargs, and print info
"""
""" Create a log using **kwargs, and print info """
log = Log(station=self.station, date=date or tz.now(), **kwargs)
if log.type == Log.Type.on_air and log.diffusion is None:
log.collision = Diffusion.objects.station(log.station) \
.on_air().at(log.date).first()
log.save()
log.print()
return log
@ -153,7 +155,7 @@ class Monitor:
# check for reruns
if not diff.is_date_in_range(air_time) and not diff.initial:
diff = Diffusion.objects.at(air_time) \
.filter(initial=diff).first()
.on_air().filter(initial=diff).first()
# log sound on air
return self.log(
@ -170,14 +172,14 @@ class Monitor:
if log.diffusion:
return
tracks = Track.objects.filter(sound=log.sound, timestamp_isnull=False)
tracks = Track.objects.filter(sound=log.sound, timestamp__isnull=False)
if not tracks.exists():
return
tracks = tracks.exclude(log__station=self.station, log__pk__gt=log.pk)
now = tz.now()
for track in tracks:
pos = log.date + tz.timedelta(seconds=track.position)
pos = log.date + tz.timedelta(seconds=track.timestamp)
if pos > now:
break
# log track on air
@ -195,14 +197,14 @@ class Monitor:
if self.sync_next and self.sync_next < now:
return
self.sync_next = now + tz.timedelta(seconds = self.sync_timeout)
self.sync_next = now + tz.timedelta(seconds=self.sync_timeout)
for source in self.station.sources:
if source == self.station.dealer:
continue
playlist = source.program.sound_set.all() \
.filter(type=Sound.Type.archive) \
.values_list('path', flat = True)
.values_list('path', flat=True)
source.playlist = list(playlist)
def trace_canceled(self):
@ -214,24 +216,24 @@ class Monitor:
return
qs = Diffusions.objects.station(self.station).at().filter(
type = Diffusion.Type.normal,
sound__type = Sound.Type.archive,
type=Diffusion.Type.normal,
sound__type=Sound.Type.archive,
)
logs = Log.objects.station(station).on_air().with_diff()
date = tz.now() - datetime.timedelta(seconds = self.cancel_timeout)
date = tz.now() - datetime.timedelta(seconds=self.cancel_timeout)
for diff in qs:
if logs.filter(diffusion = diff):
if logs.filter(diffusion=diff):
continue
if diff.start < now:
diff.type = Diffusion.Type.canceled
diff.save()
# log canceled diffusion
self.log(
type = Log.Type.other,
diffusion = diff,
comment = 'Diffusion canceled after {} seconds' \
.format(self.cancel_timeout)
type=Log.Type.other,
diffusion=diff,
comment='Diffusion canceled after {} seconds'
.format(self.cancel_timeout)
)
def __current_diff(self):
@ -251,7 +253,7 @@ class Monitor:
# last sound source change: end of file reached or forced to stop
sounds = Log.objects.station(station).on_air().with_sound() \
.filter(date__gte = log.date) \
.filter(date__gte=log.date) \
.order_by('date')
if sounds.count() and sounds.last().source != log.source:
@ -259,12 +261,12 @@ class Monitor:
# last diff is still playing: get remaining playlist
sounds = sounds \
.filter(source = log.source, pk__gt = log.pk) \
.exclude(sound__type = Sound.Type.removed)
.filter(source=log.source, pk__gt=log.pk) \
.exclude(sound__type=Sound.Type.removed)
remaining = log.diffusion.get_sounds(archive = True) \
.exclude(pk__in = sounds) \
.values_list('path', flat = True)
remaining = log.diffusion.get_sounds(archive=True) \
.exclude(pk__in=sounds) \
.values_list('path', flat=True)
return log.diffusion, list(remaining)
def __next_diff(self, diff):
@ -273,16 +275,14 @@ class Monitor:
If diff is given, it is the one to be played right after it.
"""
station = self.station
kwargs = {'start__gte': diff.end } if diff else {}
kwargs['type'] = Diffusion.Type.normal
qs = Diffusion.objects.station(station).at().filter(**kwargs) \
kwargs = {'start__gte': diff.end} if diff else {}
qs = Diffusion.objects.station(station) \
.on_air().at().filter(**kwargs) \
.distinct().order_by('start')
diff = qs.first()
return (diff, diff and diff.get_playlist(archive = True) or [])
return (diff, diff and diff.get_playlist(archive=True) or [])
def handle_pl_sync(self, source, playlist, diff = None, date = None):
def handle_pl_sync(self, source, playlist, diff=None, date=None):
"""
Update playlist of a source if required, and handle logging when
it is needed.
@ -297,11 +297,11 @@ class Monitor:
source.playlist = playlist
if diff and not diff.is_live():
# log diffusion archive load
self.log(type = Log.Type.load,
source = source.id,
diffusion = diff,
date = date,
comment = '\n'.join(playlist))
self.log(type=Log.Type.load,
source=source.id,
diffusion=diff,
date=date,
comment='\n'.join(playlist))
def handle_diff_start(self, source, diff, date):
"""
@ -318,11 +318,11 @@ class Monitor:
# live: just log it
if diff.is_live():
diff_ = Log.objects.station(self.station) \
.filter(diffusion = diff, type = Log.Type.on_air)
.filter(diffusion=diff, type=Log.Type.on_air)
if not diff_.count():
# log live diffusion
self.log(type = Log.Type.on_air, source = source.id,
diffusion = diff, date = date)
self.log(type=Log.Type.on_air, source=source.id,
diffusion=diff, date=date)
return
# enable dealer
@ -331,8 +331,8 @@ class Monitor:
last_start = self.last_diff_start
if not last_start or last_start.diffusion_id != diff.pk:
# log triggered diffusion
self.log(type = Log.Type.start, source = source.id,
diffusion = diff, date = date)
self.log(type=Log.Type.start, source=source.id,
diffusion=diff, date=date)
def handle(self):
"""
@ -358,10 +358,10 @@ class Monitor:
class Command (BaseCommand):
help= __doc__
help = __doc__
def add_arguments (self, parser):
parser.formatter_class=RawTextHelpFormatter
def add_arguments(self, parser):
parser.formatter_class = RawTextHelpFormatter
group = parser.add_argument_group('actions')
group.add_argument(
'-c', '--config', action='store_true',
@ -396,25 +396,25 @@ class Command (BaseCommand):
'check'
)
def handle (self, *args,
config = None, run = None, monitor = None,
station = [], delay = 1000, timeout = 600,
**options):
def handle(self, *args,
config=None, run=None, monitor=None,
station=[], delay=1000, timeout=600,
**options):
stations = Station.objects.filter(name__in = station)[:] \
if station else Station.objects.all()[:]
stations = Station.objects.filter(name__in=station)[:] \
if station else Station.objects.all()[:]
for station in stations:
# station.prepare()
if config and not run: # no need to write it twice
if config and not run: # no need to write it twice
station.streamer.push()
if run:
station.streamer.process_run()
if monitor:
monitors = [
Monitor(station, cancel_timeout = timeout)
for station in stations
Monitor(station, cancel_timeout=timeout)
for station in stations
]
delay = delay / 1000
while True:
@ -425,4 +425,3 @@ class Command (BaseCommand):
if run:
for station in stations:
station.controller.process_wait()