optigesr/testing11
0
1import argparse2import os3import sys4import tempfile5import time6 7import torch8import torchaudio9 10from tortoise.api import MODELS_DIR, TextToSpeech11from tortoise.utils.audio import get_voices, load_voices, load_audio12from tortoise.utils.text import split_and_recombine_text13 14parser = argparse.ArgumentParser(15 description='TorToiSe is a text-to-speech program that is capable of synthesizing speech '16 'in multiple voices with realistic prosody and intonation.')17 18parser.add_argument(19 'text', type=str, nargs='*',20 help='Text to speak. If omitted, text is read from stdin.')21parser.add_argument(22 '-v, --voice', type=str, default='random', metavar='VOICE', dest='voice',23 help='Selects the voice to use for generation. Use the & character to join two voices together. '24 'Use a comma to perform inference on multiple voices. Set to "all" to use all available voices. '25 'Note that multiple voices require the --output-dir option to be set.')26parser.add_argument(27 '-V, --voices-dir', metavar='VOICES_DIR', type=str, dest='voices_dir',28 help='Path to directory containing extra voices to be loaded. Use a comma to specify multiple directories.')29parser.add_argument(30 '-p, --preset', type=str, default='fast', choices=['ultra_fast', 'fast', 'standard', 'high_quality'], dest='preset',31 help='Which voice quality preset to use.')32parser.add_argument(33 '-q, --quiet', default=False, action='store_true', dest='quiet',34 help='Suppress all output.')35 36output_group = parser.add_mutually_exclusive_group(required=True)37output_group.add_argument(38 '-l, --list-voices', default=False, action='store_true', dest='list_voices',39 help='List available voices and exit.')40output_group.add_argument(41 '-P, --play', action='store_true', dest='play',42 help='Play the audio (requires pydub).')43output_group.add_argument(44 '-o, --output', type=str, metavar='OUTPUT', dest='output',45 help='Save the audio to a file.')46output_group.add_argument(47 '-O, --output-dir', type=str, metavar='OUTPUT_DIR', dest='output_dir',48 help='Save the audio to a directory as individual segments.')49 50multi_output_group = parser.add_argument_group('multi-output options (requires --output-dir)')51multi_output_group.add_argument(52 '--candidates', type=int, default=1,53 help='How many output candidates to produce per-voice. Note that only the first candidate is used in the combined output.')54multi_output_group.add_argument(55 '--regenerate', type=str, default=None,56 help='Comma-separated list of clip numbers to re-generate.')57multi_output_group.add_argument(58 '--skip-existing', action='store_true',59 help='Set to skip re-generating existing clips.')60 61advanced_group = parser.add_argument_group('advanced options')62advanced_group.add_argument(63 '--produce-debug-state', default=False, action='store_true',64 help='Whether or not to produce debug_states in current directory, which can aid in reproducing problems.')65advanced_group.add_argument(66 '--seed', type=int, default=None,67 help='Random seed which can be used to reproduce results.')68advanced_group.add_argument(69 '--models-dir', type=str, default=MODELS_DIR,70 help='Where to find pretrained model checkpoints. Tortoise automatically downloads these to '71 '~/.cache/tortoise/.models, so this should only be specified if you have custom checkpoints.')72advanced_group.add_argument(73 '--text-split', type=str, default=None,74 help='How big chunks to split the text into, in the format <desired_length>,<max_length>.')75advanced_group.add_argument(76 '--disable-redaction', default=False, action='store_true',77 help='Normally text enclosed in brackets are automatically redacted from the spoken output '78 '(but are still rendered by the model), this can be used for prompt engineering. '79 'Set this to disable this behavior.')80advanced_group.add_argument(81 '--device', type=str, default=None,82 help='Device to use for inference.')83advanced_group.add_argument(84 '--batch-size', type=int, default=None,85 help='Batch size to use for inference. If omitted, the batch size is set based on available GPU memory.')86 87tuning_group = parser.add_argument_group('tuning options (overrides preset settings)')88tuning_group.add_argument(89 '--num-autoregressive-samples', type=int, default=None,90 help='Number of samples taken from the autoregressive model, all of which are filtered using CLVP. '91 'As TorToiSe is a probabilistic model, more samples means a higher probability of creating something "great".')92tuning_group.add_argument(93 '--temperature', type=float, default=None,94 help='The softmax temperature of the autoregressive model.')95tuning_group.add_argument(96 '--length-penalty', type=float, default=None,97 help='A length penalty applied to the autoregressive decoder. Higher settings causes the model to produce more terse outputs.')98tuning_group.add_argument(99 '--repetition-penalty', type=float, default=None,100 help='A penalty that prevents the autoregressive decoder from repeating itself during decoding. '101 'Can be used to reduce the incidence of long silences or "uhhhhhhs", etc.')102tuning_group.add_argument(103 '--top-p', type=float, default=None,104 help='P value used in nucleus sampling. 0 to 1. Lower values mean the decoder produces more "likely" (aka boring) outputs.')105tuning_group.add_argument(106 '--max-mel-tokens', type=int, default=None,107 help='Restricts the output length. 1 to 600. Each unit is 1/20 of a second.')108tuning_group.add_argument(109 '--cvvp-amount', type=float, default=None,110 help='How much the CVVP model should influence the output.'111 'Increasing this can in some cases reduce the likelihood of multiple speakers.')112tuning_group.add_argument(113 '--diffusion-iterations', type=int, default=None,114 help='Number of diffusion steps to perform. More steps means the network has more chances to iteratively'115 'refine the output, which should theoretically mean a higher quality output. '116 'Generally a value above 250 is not noticeably better, however.')117tuning_group.add_argument(118 '--cond-free', type=bool, default=None,119 help='Whether or not to perform conditioning-free diffusion. Conditioning-free diffusion performs two forward passes for '120 'each diffusion step: one with the outputs of the autoregressive model and one with no conditioning priors. The output '121 'of the two is blended according to the cond_free_k value below. Conditioning-free diffusion is the real deal, and '122 'dramatically improves realism.')123tuning_group.add_argument(124 '--cond-free-k', type=float, default=None,125 help='Knob that determines how to balance the conditioning free signal with the conditioning-present signal. [0,inf]. '126 'As cond_free_k increases, the output becomes dominated by the conditioning-free signal. '127 'Formula is: output=cond_present_output*(cond_free_k+1)-cond_absenct_output*cond_free_k')128tuning_group.add_argument(129 '--diffusion-temperature', type=float, default=None,130 help='Controls the variance of the noise fed into the diffusion model. [0,1]. Values at 0 '131 'are the "mean" prediction of the diffusion network and will sound bland and smeared. ')132 133usage_examples = f'''134Examples:135 136Read text using random voice and place it in a file:137 138 {parser.prog} -o hello.wav "Hello, how are you?"139 140Read text from stdin and play it using the tom voice:141 142 echo "Say it like you mean it!" | {parser.prog} -P -v tom143 144Read a text file using multiple voices and save the audio clips to a directory:145 146 {parser.prog} -O /tmp/tts-results -v tom,emma <textfile.txt147'''148 149try:150 args = parser.parse_args()151except SystemExit as e:152 if e.code == 0:153 print(usage_examples)154 sys.exit(e.code)155 156extra_voice_dirs = args.voices_dir.split(',') if args.voices_dir else []157all_voices = sorted(get_voices(extra_voice_dirs))158 159if args.list_voices:160 for v in all_voices:161 print(v)162 sys.exit(0)163 164selected_voices = all_voices if args.voice == 'all' else args.voice.split(',')165selected_voices = [v.split('&') if '&' in v else [v] for v in selected_voices]166for voices in selected_voices:167 for v in voices:168 if v != 'random' and v not in all_voices:169 parser.error(f'voice {v} not available, use --list-voices to see available voices.')170 171if len(args.text) == 0:172 text = ''173 for line in sys.stdin:174 text += line175else:176 text = ' '.join(args.text)177text = text.strip()178if args.text_split:179 desired_length, max_length = [int(x) for x in args.text_split.split(',')]180 if desired_length > max_length:181 parser.error(f'--text-split: desired_length ({desired_length}) must be <= max_length ({max_length})')182 texts = split_and_recombine_text(text, desired_length, max_length)183else:184 texts = split_and_recombine_text(text)185if len(texts) == 0:186 parser.error('no text provided')187 188if args.output_dir:189 os.makedirs(args.output_dir, exist_ok=True)190else:191 if len(selected_voices) > 1:192 parser.error('cannot have multiple voices without --output-dir"')193 if args.candidates > 1:194 parser.error('cannot have multiple candidates without --output-dir"')195 196# error out early if pydub isn't installed197if args.play:198 try:199 import pydub200 import pydub.playback201 except ImportError:202 parser.error('--play requires pydub to be installed, which can be done with "pip install pydub"')203 204seed = int(time.time()) if args.seed is None else args.seed205if not args.quiet:206 print('Loading tts...')207tts = TextToSpeech(models_dir=args.models_dir, enable_redaction=not args.disable_redaction,208 device=args.device, autoregressive_batch_size=args.batch_size)209gen_settings = {210 'use_deterministic_seed': seed,211 'verbose': not args.quiet,212 'k': args.candidates,213 'preset': args.preset,214}215tuning_options = [216 'num_autoregressive_samples', 'temperature', 'length_penalty', 'repetition_penalty', 'top_p',217 'max_mel_tokens', 'cvvp_amount', 'diffusion_iterations', 'cond_free', 'cond_free_k', 'diffusion_temperature']218for option in tuning_options:219 if getattr(args, option) is not None:220 gen_settings[option] = getattr(args, option)221total_clips = len(texts) * len(selected_voices)222regenerate_clips = [int(x) for x in args.regenerate.split(',')] if args.regenerate else None223for voice_idx, voice in enumerate(selected_voices):224 audio_parts = []225 voice_samples, conditioning_latents = load_voices(voice, extra_voice_dirs)226 for text_idx, text in enumerate(texts):227 clip_name = f'{"-".join(voice)}_{text_idx:02d}'228 if args.output_dir:229 first_clip = os.path.join(args.output_dir, f'{clip_name}_00.wav')230 if (args.skip_existing or (regenerate_clips and text_idx not in regenerate_clips)) and os.path.exists(first_clip):231 audio_parts.append(load_audio(first_clip, 24000))232 if not args.quiet:233 print(f'Skipping {clip_name}')234 continue235 if not args.quiet:236 print(f'Rendering {clip_name} ({(voice_idx * len(texts) + text_idx + 1)} of {total_clips})...')237 print(' ' + text)238 gen = tts.tts_with_preset(239 text, voice_samples=voice_samples, conditioning_latents=conditioning_latents, **gen_settings)240 gen = gen if args.candidates > 1 else [gen]241 for candidate_idx, audio in enumerate(gen):242 audio = audio.squeeze(0).cpu()243 if candidate_idx == 0:244 audio_parts.append(audio)245 if args.output_dir:246 filename = f'{clip_name}_{candidate_idx:02d}.wav'247 torchaudio.save(os.path.join(args.output_dir, filename), audio, 24000)248 249 audio = torch.cat(audio_parts, dim=-1)250 if args.output_dir:251 filename = f'{"-".join(voice)}_combined.wav'252 torchaudio.save(os.path.join(args.output_dir, filename), audio, 24000)253 elif args.output:254 filename = args.output if args.output else os.tmp255 torchaudio.save(args.output, audio, 24000)256 elif args.play:257 f = tempfile.NamedTemporaryFile(suffix='.wav', delete=True)258 torchaudio.save(f.name, audio, 24000)259 pydub.playback.play(pydub.AudioSegment.from_wav(f.name))260 261 if args.produce_debug_state:262 os.makedirs('debug_states', exist_ok=True)263 dbg_state = (seed, texts, voice_samples, conditioning_latents, args)264 torch.save(dbg_state, os.path.join('debug_states', f'debug_{"-".join(voice)}.pth'))