RayeRen's picture
init
d1b91e7
import atexit
import sys
import os
import time
import argparse
from datetime import datetime
import multiprocessing as mp
from montreal_forced_aligner import __version__
from montreal_forced_aligner.utils import get_available_acoustic_languages, get_available_g2p_languages, \
get_available_dict_languages, get_available_lm_languages, get_available_ivector_languages
from montreal_forced_aligner.command_line.align import run_align_corpus
from mfa_usr.adapt import run_adapt_model
from montreal_forced_aligner.command_line.train_and_align import run_train_corpus
from montreal_forced_aligner.command_line.g2p import run_g2p
from montreal_forced_aligner.command_line.train_g2p import run_train_g2p
from montreal_forced_aligner.command_line.validate import run_validate_corpus
from montreal_forced_aligner.command_line.download import run_download
from montreal_forced_aligner.command_line.train_lm import run_train_lm
from montreal_forced_aligner.command_line.thirdparty import run_thirdparty
from montreal_forced_aligner.command_line.train_ivector_extractor import run_train_ivector_extractor
from montreal_forced_aligner.command_line.classify_speakers import run_classify_speakers
from montreal_forced_aligner.command_line.transcribe import run_transcribe_corpus
from montreal_forced_aligner.command_line.train_dictionary import run_train_dictionary
from montreal_forced_aligner.command_line.create_segments import run_create_segments
from montreal_forced_aligner.exceptions import MFAError
from montreal_forced_aligner.config import update_global_config, load_global_config, update_command_history, \
load_command_history
class ExitHooks(object):
def __init__(self):
self.exit_code = None
self.exception = None
def hook(self):
self._orig_exit = sys.exit
sys.exit = self.exit
sys.excepthook = self.exc_handler
def exit(self, code=0):
self.exit_code = code
self._orig_exit(code)
def exc_handler(self, exc_type, exc, *args):
self.exception = exc
hooks = ExitHooks()
hooks.hook()
BEGIN = time.time()
BEGIN_DATE = datetime.now()
def history_save_handler():
history_data = {
'command': ' '.join(sys.argv),
'execution_time': time.time() - BEGIN,
'date': BEGIN_DATE,
'version': __version__
}
if hooks.exit_code is not None:
history_data['exit_code'] = hooks.exit_code
history_data['exception'] = ''
elif hooks.exception is not None:
history_data['exit_code'] = 1
history_data['exception'] = hooks.exception
else:
history_data['exception'] = ''
history_data['exit_code'] = 0
update_command_history(history_data)
atexit.register(history_save_handler)
def fix_path():
from montreal_forced_aligner.config import TEMP_DIR
thirdparty_dir = os.path.join(TEMP_DIR, 'thirdparty', 'bin')
old_path = os.environ.get('PATH', '')
if sys.platform == 'win32':
os.environ['PATH'] = thirdparty_dir + ';' + old_path
else:
os.environ['PATH'] = thirdparty_dir + ':' + old_path
os.environ['LD_LIBRARY_PATH'] = thirdparty_dir + ':' + os.environ.get('LD_LIBRARY_PATH', '')
def unfix_path():
if sys.platform == 'win32':
sep = ';'
os.environ['PATH'] = sep.join(os.environ['PATH'].split(sep)[1:])
else:
sep = ':'
os.environ['PATH'] = sep.join(os.environ['PATH'].split(sep)[1:])
os.environ['LD_LIBRARY_PATH'] = sep.join(os.environ['PATH'].split(sep)[1:])
acoustic_languages = get_available_acoustic_languages()
ivector_languages = get_available_ivector_languages()
lm_languages = get_available_lm_languages()
g2p_languages = get_available_g2p_languages()
dict_languages = get_available_dict_languages()
def create_parser():
GLOBAL_CONFIG = load_global_config()
def add_global_options(subparser, textgrid_output=False):
subparser.add_argument('-t', '--temp_directory', type=str, default=GLOBAL_CONFIG['temp_directory'],
help=f"Temporary directory root to store MFA created files, default is {GLOBAL_CONFIG['temp_directory']}")
subparser.add_argument('--disable_mp',
help=f"Disable any multiprocessing during alignment (not recommended), default is {not GLOBAL_CONFIG['use_mp']}",
action='store_true',
default=not GLOBAL_CONFIG['use_mp'])
subparser.add_argument('-j', '--num_jobs', type=int, default=GLOBAL_CONFIG['num_jobs'],
help=f"Number of data splits (and cores to use if multiprocessing is enabled), defaults "
f"is {GLOBAL_CONFIG['num_jobs']}")
subparser.add_argument('-v', '--verbose', help=f"Output debug messages, default is {GLOBAL_CONFIG['verbose']}",
action='store_true',
default=GLOBAL_CONFIG['verbose'])
subparser.add_argument('--clean', help=f"Remove files from previous runs, default is {GLOBAL_CONFIG['clean']}",
action='store_true',
default=GLOBAL_CONFIG['clean'])
subparser.add_argument('--overwrite',
help=f"Overwrite output files when they exist, default is {GLOBAL_CONFIG['overwrite']}",
action='store_true',
default=GLOBAL_CONFIG['overwrite'])
subparser.add_argument('--debug',
help=f"Run extra steps for debugging issues, default is {GLOBAL_CONFIG['debug']}",
action='store_true',
default=GLOBAL_CONFIG['debug'])
if textgrid_output:
subparser.add_argument('--disable_textgrid_cleanup',
help=f"Disable extra clean up steps on TextGrid output, default is {not GLOBAL_CONFIG['cleanup_textgrids']}",
action='store_true',
default=not GLOBAL_CONFIG['cleanup_textgrids'])
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="subcommand")
subparsers.required = True
version_parser = subparsers.add_parser('version')
align_parser = subparsers.add_parser('align')
align_parser.add_argument('corpus_directory', help="Full path to the directory to align")
align_parser.add_argument('dictionary_path', help="Full path to the pronunciation dictionary to use")
align_parser.add_argument('acoustic_model_path',
help=f"Full path to the archive containing pre-trained model or language ({', '.join(acoustic_languages)})")
align_parser.add_argument('output_directory',
help="Full path to output directory, will be created if it doesn't exist")
align_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for alignment")
align_parser.add_argument('-s', '--speaker_characters', type=str, default='0',
help="Number of characters of file names to use for determining speaker, "
'default is to use directory names')
align_parser.add_argument('-a', '--audio_directory', type=str, default='',
help="Audio directory root to use for finding audio files")
add_global_options(align_parser, textgrid_output=True)
adapt_parser = subparsers.add_parser('adapt')
adapt_parser.add_argument('corpus_directory', help="Full path to the directory to align")
adapt_parser.add_argument('dictionary_path', help="Full path to the pronunciation dictionary to use")
adapt_parser.add_argument('acoustic_model_path',
help=f"Full path to the archive containing pre-trained model or language ({', '.join(acoustic_languages)})")
adapt_parser.add_argument('output_model_path',
help="Full path to save adapted_model")
adapt_parser.add_argument('output_directory',
help="Full path to output directory, will be created if it doesn't exist")
adapt_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for alignment")
adapt_parser.add_argument('-s', '--speaker_characters', type=str, default='0',
help="Number of characters of file names to use for determining speaker, "
'default is to use directory names')
adapt_parser.add_argument('-a', '--audio_directory', type=str, default='',
help="Audio directory root to use for finding audio files")
add_global_options(adapt_parser, textgrid_output=True)
train_parser = subparsers.add_parser('train')
train_parser.add_argument('corpus_directory', help="Full path to the source directory to align")
train_parser.add_argument('dictionary_path', help="Full path to the pronunciation dictionary to use",
default='')
train_parser.add_argument('output_directory',
help="Full path to output directory, will be created if it doesn't exist")
train_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for training and alignment")
train_parser.add_argument('-o', '--output_model_path', type=str, default='',
help="Full path to save resulting acoustic and dictionary model")
train_parser.add_argument('-s', '--speaker_characters', type=str, default='0',
help="Number of characters of filenames to use for determining speaker, "
'default is to use directory names')
train_parser.add_argument('-a', '--audio_directory', type=str, default='',
help="Audio directory root to use for finding audio files")
train_parser.add_argument('-m', '--acoustic_model_path', type=str, default='',
help="Full path to save adapted_model")
add_global_options(train_parser, textgrid_output=True)
validate_parser = subparsers.add_parser('validate')
validate_parser.add_argument('corpus_directory', help="Full path to the source directory to align")
validate_parser.add_argument('dictionary_path', help="Full path to the pronunciation dictionary to use",
default='')
validate_parser.add_argument('acoustic_model_path', nargs='?', default='',
help=f"Full path to the archive containing pre-trained model or language ({', '.join(acoustic_languages)})")
validate_parser.add_argument('-s', '--speaker_characters', type=str, default='0',
help="Number of characters of file names to use for determining speaker, "
'default is to use directory names')
validate_parser.add_argument('--test_transcriptions', help="Test accuracy of transcriptions", action='store_true')
validate_parser.add_argument('--ignore_acoustics',
help="Skip acoustic feature generation and associated validation",
action='store_true')
add_global_options(validate_parser)
g2p_model_help_message = f'''Full path to the archive containing pre-trained model or language ({', '.join(g2p_languages)})
If not specified, then orthographic transcription is split into pronunciations.'''
g2p_parser = subparsers.add_parser('g2p')
g2p_parser.add_argument("g2p_model_path", help=g2p_model_help_message, nargs='?')
g2p_parser.add_argument("input_path",
help="Corpus to base word list on or a text file of words to generate pronunciations")
g2p_parser.add_argument("output_path", help="Path to save output dictionary")
g2p_parser.add_argument('--include_bracketed', help="Included words enclosed by brackets, i.e. [...], (...), <...>",
action='store_true')
g2p_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for G2P")
add_global_options(g2p_parser)
train_g2p_parser = subparsers.add_parser('train_g2p')
train_g2p_parser.add_argument("dictionary_path", help="Location of existing dictionary")
train_g2p_parser.add_argument("output_model_path", help="Desired location of generated model")
train_g2p_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for G2P")
train_g2p_parser.add_argument("--validate", action='store_true',
help="Perform an analysis of accuracy training on "
"most of the data and validating on an unseen subset")
add_global_options(train_g2p_parser)
download_parser = subparsers.add_parser('download')
download_parser.add_argument("model_type",
help="Type of model to download, one of 'acoustic', 'g2p', or 'dictionary'")
download_parser.add_argument("language", help="Name of language code to download, if not specified, "
"will list all available languages", nargs='?')
train_lm_parser = subparsers.add_parser('train_lm')
train_lm_parser.add_argument('source_path', help="Full path to the source directory to train from, alternatively "
'an ARPA format language model to convert for MFA use')
train_lm_parser.add_argument('output_model_path', type=str,
help="Full path to save resulting language model")
train_lm_parser.add_argument('-m', '--model_path', type=str,
help="Full path to existing language model to merge probabilities")
train_lm_parser.add_argument('-w', '--model_weight', type=float, default=1.0,
help="Weight factor for supplemental language model, defaults to 1.0")
train_lm_parser.add_argument('--dictionary_path', help="Full path to the pronunciation dictionary to use",
default='')
train_lm_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for training and alignment")
add_global_options(train_lm_parser)
train_dictionary_parser = subparsers.add_parser('train_dictionary')
train_dictionary_parser.add_argument('corpus_directory', help="Full path to the directory to align")
train_dictionary_parser.add_argument('dictionary_path', help="Full path to the pronunciation dictionary to use")
train_dictionary_parser.add_argument('acoustic_model_path',
help=f"Full path to the archive containing pre-trained model or language ({', '.join(acoustic_languages)})")
train_dictionary_parser.add_argument('output_directory',
help="Full path to output directory, will be created if it doesn't exist")
train_dictionary_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for alignment")
train_dictionary_parser.add_argument('-s', '--speaker_characters', type=str, default='0',
help="Number of characters of file names to use for determining speaker, "
'default is to use directory names')
add_global_options(train_dictionary_parser)
train_ivector_parser = subparsers.add_parser('train_ivector')
train_ivector_parser.add_argument('corpus_directory', help="Full path to the source directory to "
'train the ivector extractor')
train_ivector_parser.add_argument('dictionary_path', help="Full path to the pronunciation dictionary to use")
train_ivector_parser.add_argument('acoustic_model_path', type=str, default='',
help="Full path to acoustic model for alignment")
train_ivector_parser.add_argument('output_model_path', type=str, default='',
help="Full path to save resulting ivector extractor")
train_ivector_parser.add_argument('-s', '--speaker_characters', type=str, default='0',
help="Number of characters of filenames to use for determining speaker, "
'default is to use directory names')
train_ivector_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for training")
add_global_options(train_ivector_parser)
classify_speakers_parser = subparsers.add_parser('classify_speakers')
classify_speakers_parser.add_argument('corpus_directory', help="Full path to the source directory to "
'run speaker classification')
classify_speakers_parser.add_argument('ivector_extractor_path', type=str, default='',
help="Full path to ivector extractor model")
classify_speakers_parser.add_argument('output_directory',
help="Full path to output directory, will be created if it doesn't exist")
classify_speakers_parser.add_argument('-s', '--num_speakers', type=int, default=0,
help="Number of speakers if known")
classify_speakers_parser.add_argument('--cluster', help="Using clustering instead of classification",
action='store_true')
classify_speakers_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for ivector extraction")
add_global_options(classify_speakers_parser)
create_segments_parser = subparsers.add_parser('create_segments')
create_segments_parser.add_argument('corpus_directory', help="Full path to the source directory to "
'run VAD segmentation')
create_segments_parser.add_argument('output_directory',
help="Full path to output directory, will be created if it doesn't exist")
create_segments_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for segmentation")
add_global_options(create_segments_parser)
transcribe_parser = subparsers.add_parser('transcribe')
transcribe_parser.add_argument('corpus_directory', help="Full path to the directory to transcribe")
transcribe_parser.add_argument('dictionary_path', help="Full path to the pronunciation dictionary to use")
transcribe_parser.add_argument('acoustic_model_path',
help=f"Full path to the archive containing pre-trained model or language ({', '.join(acoustic_languages)})")
transcribe_parser.add_argument('language_model_path',
help=f"Full path to the archive containing pre-trained model or language ({', '.join(lm_languages)})")
transcribe_parser.add_argument('output_directory',
help="Full path to output directory, will be created if it doesn't exist")
transcribe_parser.add_argument('--config_path', type=str, default='',
help="Path to config file to use for transcription")
transcribe_parser.add_argument('-s', '--speaker_characters', type=str, default='0',
help="Number of characters of file names to use for determining speaker, "
'default is to use directory names')
transcribe_parser.add_argument('-a', '--audio_directory', type=str, default='',
help="Audio directory root to use for finding audio files")
transcribe_parser.add_argument('-e', '--evaluate', help="Evaluate the transcription "
"against golden texts", action='store_true')
add_global_options(transcribe_parser)
config_parser = subparsers.add_parser('configure',
help="The configure command is used to set global defaults for MFA so "
"you don't have to set them every time you call an MFA command.")
config_parser.add_argument('-t', '--temp_directory', type=str, default='',
help=f"Set the default temporary directory, default is {GLOBAL_CONFIG['temp_directory']}")
config_parser.add_argument('-j', '--num_jobs', type=int,
help=f"Set the number of processes to use by default, defaults to {GLOBAL_CONFIG['num_jobs']}")
config_parser.add_argument('--always_clean', help="Always remove files from previous runs by default",
action='store_true')
config_parser.add_argument('--never_clean', help="Don't remove files from previous runs by default",
action='store_true')
config_parser.add_argument('--always_verbose', help="Default to verbose output", action='store_true')
config_parser.add_argument('--never_verbose', help="Default to non-verbose output", action='store_true')
config_parser.add_argument('--always_debug', help="Default to running debugging steps", action='store_true')
config_parser.add_argument('--never_debug', help="Default to not running debugging steps", action='store_true')
config_parser.add_argument('--always_overwrite', help="Always overwrite output files", action='store_true')
config_parser.add_argument('--never_overwrite', help="Never overwrite output files (if file already exists, "
"the output will be saved in the temp directory)",
action='store_true')
config_parser.add_argument('--disable_mp', help="Disable all multiprocessing (not recommended as it will usually "
"increase processing times)", action='store_true')
config_parser.add_argument('--enable_mp', help="Enable multiprocessing (recommended and enabled by default)",
action='store_true')
config_parser.add_argument('--disable_textgrid_cleanup', help="Disable postprocessing of TextGrids that cleans up "
"silences and recombines compound words and clitics",
action='store_true')
config_parser.add_argument('--enable_textgrid_cleanup', help="Enable postprocessing of TextGrids that cleans up "
"silences and recombines compound words and clitics",
action='store_true')
history_parser = subparsers.add_parser('history')
history_parser.add_argument('depth', help='Number of commands to list', nargs='?', default=10)
history_parser.add_argument('--verbose', help="Flag for whether to output additional information",
action='store_true')
annotator_parser = subparsers.add_parser('annotator')
anchor_parser = subparsers.add_parser('anchor')
thirdparty_parser = subparsers.add_parser('thirdparty')
thirdparty_parser.add_argument("command",
help="One of 'download', 'validate', or 'kaldi'")
thirdparty_parser.add_argument('local_directory',
help="Full path to the built executables to collect", nargs="?",
default='')
return parser
parser = create_parser()
def main():
parser = create_parser()
mp.freeze_support()
args, unknown = parser.parse_known_args()
for short in ['-c', '-d']:
if short in unknown:
print(f'Due to the number of options that `{short}` could refer to, it is not accepted. '
'Please specify the full argument')
sys.exit(1)
try:
fix_path()
if args.subcommand in ['align', 'train', 'train_ivector']:
from montreal_forced_aligner.thirdparty.kaldi import validate_alignment_binaries
if not validate_alignment_binaries():
print("There was an issue validating Kaldi binaries, please ensure you've downloaded them via the "
"'mfa thirdparty download' command. See 'mfa thirdparty validate' for more detailed information "
"on why this check failed.")
sys.exit(1)
elif args.subcommand in ['transcribe']:
from montreal_forced_aligner.thirdparty.kaldi import validate_transcribe_binaries
if not validate_transcribe_binaries():
print("There was an issue validating Kaldi binaries, please ensure you've downloaded them via the "
"'mfa thirdparty download' command. See 'mfa thirdparty validate' for more detailed information "
"on why this check failed. If you are on MacOS, please note that the thirdparty binaries available "
"via the download command do not contain the transcription ones. To get this functionality working "
"for the time being, please build kaldi locally and follow the instructions for running the "
"'mfa thirdparty kaldi' command.")
sys.exit(1)
elif args.subcommand in ['train_dictionary']:
from montreal_forced_aligner.thirdparty.kaldi import validate_train_dictionary_binaries
if not validate_train_dictionary_binaries():
print("There was an issue validating Kaldi binaries, please ensure you've downloaded them via the "
"'mfa thirdparty download' command. See 'mfa thirdparty validate' for more detailed information "
"on why this check failed. If you are on MacOS, please note that the thirdparty binaries available "
"via the download command do not contain the train_dictionary ones. To get this functionality working "
"for the time being, please build kaldi locally and follow the instructions for running the "
"'mfa thirdparty kaldi' command.")
sys.exit(1)
elif args.subcommand in ['g2p', 'train_g2p']:
try:
import pynini
except ImportError:
print("There was an issue importing Pynini, please ensure that it is installed. If you are on Windows, "
"please use the Windows Subsystem for Linux to use g2p functionality.")
sys.exit(1)
if args.subcommand == 'align':
run_align_corpus(args, unknown, acoustic_languages)
elif args.subcommand == 'adapt':
run_adapt_model(args, unknown, acoustic_languages)
elif args.subcommand == 'train':
run_train_corpus(args, unknown)
elif args.subcommand == 'g2p':
run_g2p(args, unknown, g2p_languages)
elif args.subcommand == 'train_g2p':
run_train_g2p(args, unknown)
elif args.subcommand == 'validate':
run_validate_corpus(args, unknown)
elif args.subcommand == 'download':
run_download(args)
elif args.subcommand == 'train_lm':
run_train_lm(args, unknown)
elif args.subcommand == 'train_dictionary':
run_train_dictionary(args, unknown)
elif args.subcommand == 'train_ivector':
run_train_ivector_extractor(args, unknown)
elif args.subcommand == 'classify_speakers':
run_classify_speakers(args, unknown)
elif args.subcommand in ['annotator', 'anchor']:
from montreal_forced_aligner.command_line.anchor import run_anchor
run_anchor(args)
elif args.subcommand == 'thirdparty':
run_thirdparty(args)
elif args.subcommand == 'transcribe':
run_transcribe_corpus(args, unknown)
elif args.subcommand == 'create_segments':
run_create_segments(args, unknown)
elif args.subcommand == 'configure':
update_global_config(args)
global GLOBAL_CONFIG
GLOBAL_CONFIG = load_global_config()
elif args.subcommand == 'history':
depth = args.depth
history = load_command_history()[-depth:]
for h in history:
if args.verbose:
print('command\tDate\tExecution time\tVersion\tExit code\tException')
for h in history:
execution_time = time.strftime('%H:%M:%S', time.gmtime(h['execution_time']))
d = h['date'].isoformat()
print(
f"{h['command']}\t{d}\t{execution_time}\t{h['version']}\t{h['exit_code']}\t{h['exception']}")
pass
else:
for h in history:
print(h['command'])
elif args.subcommand == 'version':
print(__version__)
except MFAError as e:
if getattr(args, 'debug', False):
raise
print(e)
sys.exit(1)
finally:
unfix_path()
if __name__ == '__main__':
main()