forked from rc/aircox
		
	- !88 pytest on existing tests - !89 reorganise settings (! see notes for deployment) Co-authored-by: bkfox <thomas bkfox net> Reviewed-on: rc/aircox#92
		
			
				
	
	
		
			181 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#! /usr/bin/env python3
 | 
						|
 | 
						|
"""Monitor sound files; For each program, check for:
 | 
						|
 | 
						|
- new files;
 | 
						|
- deleted files;
 | 
						|
- differences between files and sound;
 | 
						|
- quality of the files;
 | 
						|
 | 
						|
It tries to parse the file name to get the date of the diffusion of an
 | 
						|
episode and associate the file with it; We use the following format:
 | 
						|
    yyyymmdd[_n][_][name]
 | 
						|
 | 
						|
Where:
 | 
						|
    'yyyy' the year of the episode's diffusion;
 | 
						|
    'mm' the month of the episode's diffusion;
 | 
						|
    'dd' the day of the episode's diffusion;
 | 
						|
    'n' the number of the episode (if multiple episodes);
 | 
						|
    'name' the title of the sound;
 | 
						|
 | 
						|
 | 
						|
To check quality of files, call the command sound_quality_check using the
 | 
						|
parameters given by the setting SOUND_QUALITY. This script requires
 | 
						|
Sox (and soxi).
 | 
						|
"""
 | 
						|
import atexit
 | 
						|
import concurrent.futures as futures
 | 
						|
import logging
 | 
						|
import os
 | 
						|
import time
 | 
						|
from argparse import RawTextHelpFormatter
 | 
						|
 | 
						|
from django.core.management.base import BaseCommand
 | 
						|
from watchdog.observers import Observer
 | 
						|
 | 
						|
from aircox.conf import settings
 | 
						|
from aircox.management.sound_file import SoundFile
 | 
						|
from aircox.management.sound_monitor import MonitorHandler
 | 
						|
from aircox.models import Program, Sound
 | 
						|
 | 
						|
logger = logging.getLogger("aircox.commands")
 | 
						|
 | 
						|
 | 
						|
class Command(BaseCommand):
 | 
						|
    help = __doc__
 | 
						|
 | 
						|
    def report(self, program=None, component=None, *content):
 | 
						|
        if not component:
 | 
						|
            logger.info(
 | 
						|
                "%s: %s", str(program), " ".join([str(c) for c in content])
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            logger.info(
 | 
						|
                "%s, %s: %s",
 | 
						|
                str(program),
 | 
						|
                str(component),
 | 
						|
                " ".join([str(c) for c in content]),
 | 
						|
            )
 | 
						|
 | 
						|
    def scan(self):
 | 
						|
        """For all programs, scan dirs."""
 | 
						|
        logger.info("scan all programs...")
 | 
						|
        programs = Program.objects.filter()
 | 
						|
 | 
						|
        dirs = []
 | 
						|
        for program in programs:
 | 
						|
            logger.info("#%d %s", program.id, program.title)
 | 
						|
            self.scan_for_program(
 | 
						|
                program,
 | 
						|
                settings.SOUND_ARCHIVES_SUBDIR,
 | 
						|
                type=Sound.TYPE_ARCHIVE,
 | 
						|
            )
 | 
						|
            self.scan_for_program(
 | 
						|
                program,
 | 
						|
                settings.SOUND_EXCERPTS_SUBDIR,
 | 
						|
                type=Sound.TYPE_EXCERPT,
 | 
						|
            )
 | 
						|
            dirs.append(os.path.join(program.abspath))
 | 
						|
        return dirs
 | 
						|
 | 
						|
    def scan_for_program(self, program, subdir, **sound_kwargs):
 | 
						|
        """Scan a given directory that is associated to the given program, and
 | 
						|
        update sounds information."""
 | 
						|
        logger.info("- %s/", subdir)
 | 
						|
        if not program.ensure_dir(subdir):
 | 
						|
            return
 | 
						|
 | 
						|
        subdir = os.path.join(program.abspath, subdir)
 | 
						|
        sounds = []
 | 
						|
 | 
						|
        # sounds in directory
 | 
						|
        for path in os.listdir(subdir):
 | 
						|
            path = os.path.join(subdir, path)
 | 
						|
            if not path.endswith(settings.SOUND_FILE_EXT):
 | 
						|
                continue
 | 
						|
 | 
						|
            sound_file = SoundFile(path)
 | 
						|
            sound_file.sync(program=program, **sound_kwargs)
 | 
						|
            sounds.append(sound_file.sound.pk)
 | 
						|
 | 
						|
        # sounds in db & unchecked
 | 
						|
        sounds = Sound.objects.filter(file__startswith=subdir).exclude(
 | 
						|
            pk__in=sounds
 | 
						|
        )
 | 
						|
        self.check_sounds(sounds, program=program)
 | 
						|
 | 
						|
    def check_sounds(self, qs, **sync_kwargs):
 | 
						|
        """Only check for the sound existence or update."""
 | 
						|
        # check files
 | 
						|
        for sound in qs:
 | 
						|
            if sound.check_on_file():
 | 
						|
                SoundFile(sound.file.path).sync(sound=sound, **sync_kwargs)
 | 
						|
 | 
						|
    def monitor(self):
 | 
						|
        """Run in monitor mode."""
 | 
						|
        with futures.ThreadPoolExecutor() as pool:
 | 
						|
            archives_handler = MonitorHandler(
 | 
						|
                settings.SOUND_ARCHIVES_SUBDIR,
 | 
						|
                pool,
 | 
						|
                type=Sound.TYPE_ARCHIVE,
 | 
						|
            )
 | 
						|
            excerpts_handler = MonitorHandler(
 | 
						|
                settings.SOUND_EXCERPTS_SUBDIR,
 | 
						|
                pool,
 | 
						|
                type=Sound.TYPE_EXCERPT,
 | 
						|
            )
 | 
						|
 | 
						|
            observer = Observer()
 | 
						|
            observer.schedule(
 | 
						|
                archives_handler,
 | 
						|
                settings.PROGRAMS_DIR_ABS,
 | 
						|
                recursive=True,
 | 
						|
            )
 | 
						|
            observer.schedule(
 | 
						|
                excerpts_handler,
 | 
						|
                settings.PROGRAMS_DIR_ABS,
 | 
						|
                recursive=True,
 | 
						|
            )
 | 
						|
            observer.start()
 | 
						|
 | 
						|
            def leave():
 | 
						|
                observer.stop()
 | 
						|
                observer.join()
 | 
						|
 | 
						|
            atexit.register(leave)
 | 
						|
 | 
						|
            while True:
 | 
						|
                time.sleep(1)
 | 
						|
 | 
						|
    def add_arguments(self, parser):
 | 
						|
        parser.formatter_class = RawTextHelpFormatter
 | 
						|
        parser.add_argument(
 | 
						|
            "-q",
 | 
						|
            "--quality_check",
 | 
						|
            action="store_true",
 | 
						|
            help="Enable quality check using sound_quality_check on all "
 | 
						|
            "sounds marqued as not good",
 | 
						|
        )
 | 
						|
        parser.add_argument(
 | 
						|
            "-s",
 | 
						|
            "--scan",
 | 
						|
            action="store_true",
 | 
						|
            help="Scan programs directories for changes, plus check for a "
 | 
						|
            " matching diffusion on sounds that have not been yet assigned",
 | 
						|
        )
 | 
						|
        parser.add_argument(
 | 
						|
            "-m",
 | 
						|
            "--monitor",
 | 
						|
            action="store_true",
 | 
						|
            help="Run in monitor mode, watch for modification in the "
 | 
						|
            "filesystem and react in consequence",
 | 
						|
        )
 | 
						|
 | 
						|
    def handle(self, *args, **options):
 | 
						|
        if options.get("scan"):
 | 
						|
            self.scan()
 | 
						|
        # if options.get('quality_check'):
 | 
						|
        #    self.check_quality(check=(not options.get('scan')))
 | 
						|
        if options.get("monitor"):
 | 
						|
            self.monitor()
 |