forked from rc/aircox
add episode: models, admin, diffusion gen, sounds_monitor, playlist import
This commit is contained in:
@ -15,60 +15,57 @@ 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 datetime
|
||||
import logging
|
||||
from argparse import RawTextHelpFormatter
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from aircox.models import *
|
||||
from aircox.models import Schedule, Diffusion
|
||||
|
||||
logger = logging.getLogger('aircox.tools')
|
||||
|
||||
|
||||
class Actions:
|
||||
@classmethod
|
||||
def update(cl, date, mode):
|
||||
manual = (mode == 'manual')
|
||||
date = None
|
||||
|
||||
count = [0, 0]
|
||||
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)
|
||||
count[0] += len(items)
|
||||
def __init__(self, date):
|
||||
self.date = date or datetime.date.today()
|
||||
|
||||
# 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 conflicts.count():
|
||||
item.conflicts.set(conflicts.all())
|
||||
def update(self):
|
||||
episodes, diffusions = [], []
|
||||
for schedule in Schedule.objects.filter(program__active=True,
|
||||
initial__isnull=True):
|
||||
eps, diffs = schedule.diffusions_of_month(self.date)
|
||||
|
||||
logger.info('[update] schedule %s: %d new diffusions',
|
||||
str(schedule), len(items),
|
||||
)
|
||||
episodes += eps
|
||||
diffusions += diffs
|
||||
|
||||
logger.info('[update] %d diffusions have been created, %s', count[0],
|
||||
'do not forget manual approval' if manual else
|
||||
'{} conflicts found'.format(count[1]))
|
||||
logger.info('[update] %s: %d episodes, %d diffusions and reruns',
|
||||
str(schedule), len(eps), len(diffs))
|
||||
|
||||
@staticmethod
|
||||
def clean(date):
|
||||
with transaction.atomic():
|
||||
logger.info('[update] save %d episodes and %d diffusions',
|
||||
len(episodes), len(diffusions))
|
||||
for episode in episodes:
|
||||
episode.save()
|
||||
for diffusion in diffusions:
|
||||
# force episode id's update
|
||||
diffusion.episode = diffusion.episode
|
||||
diffusion.save()
|
||||
|
||||
def clean(self):
|
||||
qs = Diffusion.objects.filter(type=Diffusion.Type.unconfirmed,
|
||||
start__lt=date)
|
||||
start__lt=self.date)
|
||||
logger.info('[clean] %d diffusions will be removed', qs.count())
|
||||
qs.delete()
|
||||
|
||||
@staticmethod
|
||||
def check(date):
|
||||
def check(self):
|
||||
# TODO: redo
|
||||
qs = Diffusion.objects.filter(type=Diffusion.Type.unconfirmed,
|
||||
start__gt=date)
|
||||
start__gt=self.date)
|
||||
items = []
|
||||
for diffusion in qs:
|
||||
schedules = Schedule.objects.filter(program=diffusion.program)
|
||||
@ -88,21 +85,21 @@ class Command(BaseCommand):
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.formatter_class = RawTextHelpFormatter
|
||||
now = tz.datetime.today()
|
||||
today = datetime.date.today()
|
||||
|
||||
group = parser.add_argument_group('action')
|
||||
group.add_argument(
|
||||
'--update', action='store_true',
|
||||
'-u', '--update', action='store_true',
|
||||
help='generate (unconfirmed) diffusions for the given month. '
|
||||
'These diffusions must be confirmed manually by changing '
|
||||
'their type to "normal"'
|
||||
)
|
||||
group.add_argument(
|
||||
'--clean', action='store_true',
|
||||
'-l', '--clean', action='store_true',
|
||||
help='remove unconfirmed diffusions older than the given month'
|
||||
)
|
||||
group.add_argument(
|
||||
'--check', action='store_true',
|
||||
'-c', '--check', action='store_true',
|
||||
help='check unconfirmed later diffusions from the given '
|
||||
'date agains\'t schedule. If no schedule is found, remove '
|
||||
'it.'
|
||||
@ -110,10 +107,10 @@ class Command(BaseCommand):
|
||||
|
||||
group = parser.add_argument_group('date')
|
||||
group.add_argument(
|
||||
'--year', type=int, default=now.year,
|
||||
'--year', type=int, default=today.year,
|
||||
help='used by update, default is today\'s year')
|
||||
group.add_argument(
|
||||
'--month', type=int, default=now.month,
|
||||
'--month', type=int, default=today.month,
|
||||
help='used by update, default is today\'s month')
|
||||
group.add_argument(
|
||||
'--next-month', action='store_true',
|
||||
@ -121,31 +118,20 @@ class Command(BaseCommand):
|
||||
' (if next month from today'
|
||||
)
|
||||
|
||||
group = parser.add_argument_group('options')
|
||||
group.add_argument(
|
||||
'--mode', type=str, choices=['manual', 'auto'],
|
||||
default='auto',
|
||||
help='manual means that all generated diffusions are unconfirmed, '
|
||||
'thus must be approved manually; auto confirmes all '
|
||||
'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)
|
||||
date = tz.make_aware(date)
|
||||
date = datetime.date(year=options['year'], month=options['month'],
|
||||
day=1)
|
||||
if options.get('next_month'):
|
||||
month = options.get('month')
|
||||
date += tz.timedelta(days=28)
|
||||
if date.month == month:
|
||||
date += tz.timedelta(days=28)
|
||||
|
||||
date = date.replace(day=1)
|
||||
|
||||
actions = Actions(date)
|
||||
if options.get('update'):
|
||||
Actions.update(date, mode=options.get('mode'))
|
||||
actions.update()
|
||||
if options.get('clean'):
|
||||
Actions.clean(date)
|
||||
actions.clean()
|
||||
if options.get('check'):
|
||||
Actions.check(date)
|
||||
actions.check()
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""
|
||||
Import one or more playlist for the given sound. Attach it to the sound
|
||||
or to the related Diffusion if wanted.
|
||||
Import one or more playlist for the given sound. Attach it to the provided
|
||||
sound.
|
||||
|
||||
Playlists are in CSV format, where columns are separated with a
|
||||
'{settings.AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER}'. Text quote is
|
||||
@ -18,14 +18,15 @@ from argparse import RawTextHelpFormatter
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from aircox import settings
|
||||
from aircox.models import *
|
||||
import aircox.settings as settings
|
||||
|
||||
__doc__ = __doc__.format(settings=settings)
|
||||
|
||||
logger = logging.getLogger('aircox.tools')
|
||||
|
||||
|
||||
class Importer:
|
||||
class PlaylistImport:
|
||||
path = None
|
||||
data = None
|
||||
tracks = None
|
||||
@ -121,17 +122,12 @@ class Command (BaseCommand):
|
||||
help='generate a playlist for the sound of the given path. '
|
||||
'If not given, try to match a sound with the same path.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--diffusion', '-d', action='store_true',
|
||||
help='try to get the diffusion relative to the sound if it exists'
|
||||
)
|
||||
|
||||
def handle(self, path, *args, **options):
|
||||
# FIXME: absolute/relative path of sounds vs given path
|
||||
if options.get('sound'):
|
||||
sound = Sound.objects.filter(
|
||||
path__icontains=options.get('sound')
|
||||
).first()
|
||||
sound = Sound.objects.filter(path__icontains=options.get('sound'))\
|
||||
.first()
|
||||
else:
|
||||
path_, ext = os.path.splitext(path)
|
||||
sound = Sound.objects.filter(path__icontains=path_).first()
|
||||
@ -141,11 +137,10 @@ class Command (BaseCommand):
|
||||
'{path}'.format(path=path))
|
||||
return
|
||||
|
||||
if options.get('diffusion') and sound.diffusion:
|
||||
sound = sound.diffusion
|
||||
|
||||
importer = Importer(path, sound=sound).run()
|
||||
# FIXME: auto get sound.episode if any
|
||||
importer = PlaylistImport(path, sound=sound).run()
|
||||
for track in importer.tracks:
|
||||
logger.info('track #{pos} imported: {title}, by {artist}'.format(
|
||||
pos=track.position, title=track.title, artist=track.artist
|
||||
))
|
||||
|
||||
|
@ -23,6 +23,7 @@ parameters given by the setting AIRCOX_SOUND_QUALITY. This script requires
|
||||
Sox (and soxi).
|
||||
"""
|
||||
from argparse import RawTextHelpFormatter
|
||||
import datetime
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
@ -37,13 +38,21 @@ from django.conf import settings as main_settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from aircox.models import *
|
||||
import aircox.settings as settings
|
||||
import aircox.utils as utils
|
||||
from aircox import settings, utils
|
||||
from aircox.models import Diffusion, Program, Sound
|
||||
from .import_playlist import PlaylistImport
|
||||
|
||||
logger = logging.getLogger('aircox.tools')
|
||||
|
||||
|
||||
sound_path_re = re.compile(
|
||||
'^(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})'
|
||||
'(_(?P<hour>[0-9]{2})h(?P<minute>[0-9]{2}))?'
|
||||
'(_(?P<n>[0-9]+))?'
|
||||
'_?(?P<name>.*)$'
|
||||
)
|
||||
|
||||
|
||||
class SoundInfo:
|
||||
name = ''
|
||||
sound = None
|
||||
@ -66,33 +75,19 @@ class SoundInfo:
|
||||
Parse file name to get info on the assumption it has the correct
|
||||
format (given in Command.help)
|
||||
"""
|
||||
file_name = os.path.basename(value)
|
||||
file_name = os.path.splitext(file_name)[0]
|
||||
r = re.search('^(?P<year>[0-9]{4})'
|
||||
'(?P<month>[0-9]{2})'
|
||||
'(?P<day>[0-9]{2})'
|
||||
'(_(?P<hour>[0-9]{2})h(?P<minute>[0-9]{2}))?'
|
||||
'(_(?P<n>[0-9]+))?'
|
||||
'_?(?P<name>.*)$',
|
||||
file_name)
|
||||
|
||||
if not (r and r.groupdict()):
|
||||
r = {'name': file_name}
|
||||
logger.info('file name can not be parsed -> %s', value)
|
||||
else:
|
||||
r = r.groupdict()
|
||||
name = os.path.splitext(os.path.basename(value))[0]
|
||||
match = sound_path_re.search(name)
|
||||
match = match.groupdict() if match and match.groupdict() else \
|
||||
{'name': name}
|
||||
|
||||
self._path = value
|
||||
self.name = r['name'].replace('_', ' ').capitalize()
|
||||
self.name = match['name'].replace('_', ' ').capitalize()
|
||||
|
||||
for key in ('year', 'month', 'day', 'hour', 'minute'):
|
||||
value = r.get(key)
|
||||
if value is not None:
|
||||
value = int(value)
|
||||
setattr(self, key, value)
|
||||
value = match.get(key)
|
||||
setattr(self, key, int(value) if value is not None else None)
|
||||
|
||||
self.n = r.get('n')
|
||||
return r
|
||||
self.n = match.get('n')
|
||||
|
||||
def __init__(self, path='', sound=None):
|
||||
self.path = path
|
||||
@ -116,9 +111,8 @@ class SoundInfo:
|
||||
(if save is True, sync to DB), and check for a playlist file.
|
||||
"""
|
||||
sound, created = Sound.objects.get_or_create(
|
||||
path=self.path,
|
||||
defaults=kwargs
|
||||
)
|
||||
path=self.path, defaults=kwargs)
|
||||
|
||||
if created or sound.check_on_file():
|
||||
logger.info('sound is new or have been modified -> %s', self.path)
|
||||
sound.duration = self.get_duration()
|
||||
@ -139,22 +133,17 @@ class SoundInfo:
|
||||
if sound.track_set.count():
|
||||
return
|
||||
|
||||
import aircox.management.commands.import_playlist \
|
||||
as import_playlist
|
||||
|
||||
# no playlist, try to retrieve metadata
|
||||
# import playlist
|
||||
path = os.path.splitext(self.sound.path)[0] + '.csv'
|
||||
if not os.path.exists(path):
|
||||
if use_default:
|
||||
track = sound.file_metadata()
|
||||
if track:
|
||||
track.save()
|
||||
return
|
||||
if os.path.exists(path):
|
||||
PlaylistImport(path, sound=sound).run()
|
||||
# try metadata
|
||||
elif use_default:
|
||||
track = sound.file_metadata()
|
||||
if track:
|
||||
track.save()
|
||||
|
||||
# else, import
|
||||
import_playlist.Importer(path, sound=sound).run()
|
||||
|
||||
def find_diffusion(self, program, save=True):
|
||||
def find_episode(self, program, save=True):
|
||||
"""
|
||||
For a given program, check if there is an initial diffusion
|
||||
to associate to, using the date info we have. Update self.sound
|
||||
@ -163,25 +152,22 @@ class SoundInfo:
|
||||
We only allow initial diffusion since there should be no
|
||||
rerun.
|
||||
"""
|
||||
if self.year == None or not self.sound or self.sound.diffusion:
|
||||
if self.year is None or not self.sound or self.sound.episode:
|
||||
return
|
||||
|
||||
if self.hour is None:
|
||||
date = datetime.date(self.year, self.month, self.day)
|
||||
else:
|
||||
date = datetime.datetime(self.year, self.month, self.day,
|
||||
self.hour or 0, self.minute or 0)
|
||||
date = tz.datetime(self.year, self.month, self.day,
|
||||
self.hour or 0, self.minute or 0)
|
||||
date = tz.get_current_timezone().localize(date)
|
||||
|
||||
qs = Diffusion.objects.station(program.station).after(date) \
|
||||
.filter(program=program, initial__isnull=True)
|
||||
diffusion = qs.first()
|
||||
diffusion = program.diffusion_set.initial().at(date).first()
|
||||
if not diffusion:
|
||||
return
|
||||
|
||||
logger.info('diffusion %s mathes to sound -> %s', str(diffusion),
|
||||
self.sound.path)
|
||||
self.sound.diffusion = diffusion
|
||||
logger.info('%s <--> %s', self.sound.path, str(diffusion.episode))
|
||||
self.sound.episode = diffusion.episode
|
||||
if save:
|
||||
self.sound.save()
|
||||
return diffusion
|
||||
@ -219,7 +205,7 @@ class MonitorHandler(PatternMatchingEventHandler):
|
||||
self.sound_kwargs['program'] = program
|
||||
si.get_sound(save=True, **self.sound_kwargs)
|
||||
if si.year is not None:
|
||||
si.find_diffusion(program)
|
||||
si.find_episode(program)
|
||||
si.sound.save(True)
|
||||
|
||||
def on_deleted(self, event):
|
||||
@ -246,7 +232,7 @@ class MonitorHandler(PatternMatchingEventHandler):
|
||||
if program:
|
||||
si = SoundInfo(sound.path, sound=sound)
|
||||
if si.year is not None:
|
||||
si.find_diffusion(program)
|
||||
si.find_episode(program)
|
||||
sound.save()
|
||||
|
||||
|
||||
@ -270,7 +256,7 @@ class Command(BaseCommand):
|
||||
|
||||
dirs = []
|
||||
for program in programs:
|
||||
logger.info('#%d %s', program.id, program.name)
|
||||
logger.info('#%d %s', program.id, program.title)
|
||||
self.scan_for_program(
|
||||
program, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
|
||||
type=Sound.Type.archive,
|
||||
@ -304,7 +290,7 @@ class Command(BaseCommand):
|
||||
si = SoundInfo(path)
|
||||
sound_kwargs['program'] = program
|
||||
si.get_sound(save=True, **sound_kwargs)
|
||||
si.find_diffusion(program, save=True)
|
||||
si.find_episode(program, save=True)
|
||||
si.find_playlist(si.sound)
|
||||
sounds.append(si.sound.pk)
|
||||
|
||||
|
@ -216,7 +216,7 @@ class Monitor:
|
||||
return
|
||||
|
||||
qs = Diffusions.objects.station(self.station).at().filter(
|
||||
type=Diffusion.Type.normal,
|
||||
type=Diffusion.Type.on_air,
|
||||
sound__type=Sound.Type.archive,
|
||||
)
|
||||
logs = Log.objects.station(station).on_air().with_diff()
|
||||
|
Reference in New Issue
Block a user