mirror of
https://github.com/YuzuZensai/spleeter.git
synced 2026-01-30 12:22:58 +00:00
🐛 🎨 WIP: typer integration
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user