CoolFace
Apppublic

optigesr/testing11

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
tortoise_tts.py264 linesDownload Raw Back to root
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'))