From f02bcbd9c75fcb1835dae2c440c6f6bf8790f256 Mon Sep 17 00:00:00 2001 From: Faylixe Date: Mon, 7 Dec 2020 16:08:12 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20=F0=9F=8E=A8=20=20WIP:=20typer?= =?UTF-8?q?=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spleeter/__main__.py | 202 ++++++++++++++++++++++++++-------- spleeter/audio/__init__.py | 8 ++ spleeter/commands/__init__.py | 176 +++++++++++++---------------- spleeter/separator.py | 2 + 4 files changed, 245 insertions(+), 143 deletions(-) diff --git a/spleeter/__main__.py b/spleeter/__main__.py index e4f3f4a..d60af24 100644 --- a/spleeter/__main__.py +++ b/spleeter/__main__.py @@ -1,58 +1,174 @@ #!/usr/bin/env python # coding: utf8 -""" - Python oneliner script usage. +""" TO DOCUMENT """ - USAGE: python -m spleeter {train,evaluate,separate} ... -""" +from pathlib import Path +from os.path import join +from spleeter.separator import STFTBackend +from tempfile import gettempdir +from typing import List -import sys -import warnings +from .audio import Codec +from .audio.adapter import AudioAdapter +from .separator import Separator -from . import SpleeterError -from .commands import create_argument_parser -from .utils.configuration import load_configuration -from .utils.logging import ( - enable_logging, - enable_tensorflow_logging, - get_logger) +# pyright: reportMissingImports=false +# pylint: disable=import-error +from typer import Argument, Option, Typer +from typer.models import OptionInfo +# pylint: enable=import-error -__email__ = 'spleeter@deezer.com' -__author__ = 'Deezer Research' -__license__ = 'MIT License' +spleeter: Typer = Typer() +""" """ + +AudioOutput: OptionInfo = Option( + join(gettempdir(), 'separated_audio'), + help='Path of the output directory to write audio files in') + +AudioSTFTBackend: OptionInfo = Option( + STFTBackend.AUTO, + '--stft-backend', + '-B', + case_sensitive=False, + help=( + 'Who should be in charge of computing the stfts. Librosa is faster ' + 'than tensorflow on CPU and uses less memory. "auto" will use ' + 'tensorflow when GPU acceleration is available and librosa when not')) + +AudioAdapterDescriptor: OptionInfo = Option( + 'spleeter.audio.ffmpeg.FFMPEGProcessAudioAdapter', + help='Name of the audio adapter to use for audio I/O') + +MWF: OptionInfo = Option( + False, + '--mwf', + help='Whether to use multichannel Wiener filtering for separation') + +ModelParameters: OptionInfo = Option( + 'spleeter:2stems', + help='JSON filename that contains params') + +Verbose: OptionInfo = Option( + False, + '--verbose', + help='Enable verbose logs') -def main(argv): - """ Spleeter runner. Parse provided command line arguments - and run entrypoint for required command (either train, - evaluate or separate). - - :param argv: Provided command line arguments. +@spleeter.command() +def train( + adapter=None, + verbose: bool = Verbose, + params_filename: str = ModelParameters, + data: Path = Option( + ..., + exists=True, + dir_okay=True, + file_okay=False, + readable=True, + resolve_path=True, + help='Path of the folder containing audio data for training') + ) -> None: """ - try: - parser = create_argument_parser() - arguments = parser.parse_args(argv[1:]) - enable_logging() - if arguments.verbose: - enable_tensorflow_logging() - if arguments.command == 'separate': - from .commands.separate import entrypoint - elif arguments.command == 'train': - from .commands.train import entrypoint - elif arguments.command == 'evaluate': - from .commands.evaluate import entrypoint - params = load_configuration(arguments.configuration) - entrypoint(arguments, params) - except SpleeterError as e: - get_logger().error(e) + Train a source separation model + """ + pass -def entrypoint(): - """ Command line entrypoint. """ - warnings.filterwarnings('ignore') - main(sys.argv) +@spleeter.command() +def evaluate( + adapter: str = AudioAdapterDescriptor, + output_path: Path = AudioOutput, + stft_backend: STFTBackend = AudioSTFTBackend, + params_filename: str = ModelParameters, + mwf: bool = MWF, + verbose: bool = Verbose, + mus_dir: Path = Option( + ..., + '--mus_dir', + exists=True, + dir_okay=True, + file_okay=False, + readable=True, + resolve_path=True, + help='Path to musDB dataset directory') + ) -> None: + """ + Evaluate a model on the musDB test dataset + """ + pass + + +@spleeter.commmand() +def separate( + adapter: str = AudioAdapterDescriptor, + output_path: Path = AudioOutput, + stft_backend: STFTBackend = AudioSTFTBackend, + params_filename: str = ModelParameters, + mwf: bool = MWF, + verbose: bool = Verbose, + files: List[Path] = Argument( + ..., + help='List of input audio file path', + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + resolve_path=True), + filename_format: str = Option( + '{filename}/{instrument}.{codec}', + help=( + 'Template string that will be formatted to generated' + 'output filename. Such template should be Python formattable' + 'string, and could use {filename}, {instrument}, and {codec}' + 'variables')), + duration: float = Option( + 600., + help=( + 'Set a maximum duration for processing audio ' + '(only separate offset + duration first seconds of ' + 'the input file)')), + offset: float = Option( + 0., + '--offset', + '-s', + help='Set the starting offset to separate audio from'), + codec: Codec = Option( + Codec.WAV, + help='Audio codec to be used for the separated output'), + bitrate: str = Option( + '128k', + help='Audio bitrate to be used for the separated output') + ) -> None: + """ + Separate audio file(s) + """ + # TODO: try / catch or custom decorator for function handling. + # TODO: enable_logging() + # TODO: handle MWF + if verbose: + # TODO: enable_tensorflow_logging() + pass + # PREV: params = load_configuration(arguments.configuration) + audio_adapter: AudioAdapter = AudioAdapter.get(adapter) + separator: Separator = Separator( + params_filename, + MWF=MWF, + stft_backend=stft_backend) + for filename in files: + separator.separate_to_file( + filename, + output_path, + audio_adapter=audio_adapter, + offset=offset, + duration=duration, + codec=codec, + bitrate=bitrate, + filename_format=filename_format, + synchronous=False) + separator.join() if __name__ == '__main__': - entrypoint() + # TODO: warnings.filterwarnings('ignore') + spleeter() diff --git a/spleeter/audio/__init__.py b/spleeter/audio/__init__.py index 8f1343f..18efade 100644 --- a/spleeter/audio/__init__.py +++ b/spleeter/audio/__init__.py @@ -26,3 +26,11 @@ class Codec(str, Enum): M4A: str = 'm4a' WMA: str = 'wma' FLAC: str = 'flac' + + +class STFTBackend(str, Enum): + """ Enumeration of supported STFT backend. """ + + AUTO: str = 'auto' + TENSORFLOW: str = 'tensorflow' + LIBROSA: str = 'librosa' diff --git a/spleeter/commands/__init__.py b/spleeter/commands/__init__.py index 241a9bc..db7d172 100644 --- a/spleeter/commands/__init__.py +++ b/spleeter/commands/__init__.py @@ -3,61 +3,41 @@ """ This modules provides spleeter command as well as CLI parsing methods. """ -import json -import logging -from argparse import ArgumentParser from tempfile import gettempdir -from os.path import exists, join +from os.path import join + +from ..separator import STFTBackend +from ..audio import Codec + +from typer import Argument, Option +from typer.models import ArgumentInfo, OptionInfo __email__ = 'spleeter@deezer.com' __author__ = 'Deezer Research' __license__ = 'MIT License' +AudioInput: ArgumentInfo = Argument( + ..., + help='List of input audio file path', + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + resolve_path=True) +AudioOutput: OptionInfo = Option( + join(gettempdir(), 'separated_audio'), + '--output_path', + '-o', + help='Path of the output directory to write audio files in') -# -i opt specification (separate). -OPT_INPUT = { - 'dest': 'inputs', - 'nargs': '+', - 'help': 'List of input audio filenames', - 'required': True -} - -# -o opt specification (evaluate and separate). -OPT_OUTPUT = { - 'dest': 'output_path', - 'default': join(gettempdir(), 'separated_audio'), - 'help': 'Path of the output directory to write audio files in' -} - -# -f opt specification (separate). -OPT_FORMAT = { - 'dest': 'filename_format', - 'default': '{filename}/{instrument}.{codec}', - 'help': ( - 'Template string that will be formatted to generated' - 'output filename. Such template should be Python formattable' - 'string, and could use {filename}, {instrument}, and {codec}' - 'variables.' - ) -} - -# -p opt specification (train, evaluate and separate). -OPT_PARAMS = { - 'dest': 'configuration', - 'default': 'spleeter:2stems', - 'type': str, - 'action': 'store', - 'help': 'JSON filename that contains params' -} - -Offset: OptionInfo = Option( +AudioOffset: OptionInfo = Option( 0., '--offset', '-s', help='Set the starting offset to separate audio from') -Duration: OptionInfo = Option( +AudioDuration: OptionInfo = Option( 600., '--duration', '-d', @@ -66,16 +46,25 @@ Duration: OptionInfo = Option( '(only separate offset + duration first seconds of ' 'the input file)')) +FilenameFormat: OptionInfo = Option( + '{filename}/{instrument}.{codec}', + '--filename_format', + '-f', + help=( + 'Template string that will be formatted to generated' + 'output filename. Such template should be Python formattable' + 'string, and could use {filename}, {instrument}, and {codec}' + 'variables')) -class STFTBackendEnum(Enum, str): - - AUTO: str - TENSORFLOW: str - LIBROSA: str +ModelParameters: OptionInfo = Option( + 'spleeter:2stems', + '--params_filename', + '-p', + help='JSON filename that contains params') -STFTBackend: OptionInfo = Option( - STFTBackendEnum.AUTO, +AudioSTFTBackend: OptionInfo = Option( + STFTBackend.AUTO, '--stft-backend', '-B', case_sensitive=False, @@ -84,67 +73,54 @@ STFTBackend: OptionInfo = Option( 'than tensorflow on CPU and uses less memory. "auto" will use ' 'tensorflow when GPU acceleration is available and librosa when not')) +AudioCodec: OptionInfo = Option( + Codec.WAV, + '--codec', + '-c', + help='Audio codec to be used for the separated output') -# -c opt specification (separate). -OPT_CODEC = { - 'dest': 'codec', - 'choices': ('wav', 'mp3', 'ogg', 'm4a', 'wma', 'flac'), - 'default': 'wav', - 'help': 'Audio codec to be used for the separated output' -} +AudioBitrate: OptionInfo = Option( + '128k', + '--bitrate', + '-b', + help='Audio bitrate to be used for the separated output') -# -b opt specification (separate). -OPT_BITRATE = { - 'dest': 'bitrate', - 'default': '128k', - 'help': 'Audio bitrate to be used for the separated output' -} - -# -m opt specification (evaluate and separate). -OPT_MWF = { - 'dest': 'MWF', - 'action': 'store_const', - 'const': True, - 'default': False, - 'help': 'Whether to use multichannel Wiener filtering for separation', -} - -# --mus_dir opt specification (evaluate). -OPT_MUSDB = { - 'dest': 'mus_dir', - 'type': str, - 'required': True, - 'help': 'Path to folder with musDB' -} - -# -d opt specification (train). -OPT_DATA = { - 'dest': 'audio_path', - 'type': str, - 'required': True, - 'help': 'Path of the folder containing audio data for training' -} - -# -a opt specification (train, evaluate and separate). -OPT_ADAPTER = { - 'dest': 'audio_adapter', - 'type': str, - 'help': 'Name of the audio adapter to use for audio I/O' -} +MWF: OptionInfo = Option( + False, + '--mwf', + help='Whether to use multichannel Wiener filtering for separation') +MUSDBDirectory: OptionInfo = Option( + ..., + '--mus_dir', + exists=True, + dir_okay=True, + file_okay=False, + readable=True, + resolve_path=True, + help='Path to musDB dataset directory') +TrainingDataDirectory: OptionInfo = Option( + ..., + '--data', + '-d', + exists=True, + dir_okay=True, + file_okay=False, + readable=True, + resolve_path=True, + help='Path of the folder containing audio data for training') AudioAdapter: OptionInfo = Option( 'spleeter.audio.ffmpeg.FFMPEGProcessAudioAdapter', '--adapter', + '-a', help='Name of the audio adapter to use for audio I/O') - -# -a opt specification (train, evaluate and separate). -OPT_VERBOSE = { - 'action': 'store_true', - 'help': 'Shows verbose logs' -} +Verbose: OptionInfo = Option( + False, + '--verbose', + help='Enable verbose logs') def _add_common_options(parser): diff --git a/spleeter/separator.py b/spleeter/separator.py index 131bdac..cc87b68 100644 --- a/spleeter/separator.py +++ b/spleeter/separator.py @@ -16,6 +16,8 @@ import atexit import os import logging +from enum import Enum + from multiprocessing import Pool from os.path import basename, join, splitext, dirname from time import time