-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy path__main__.py
205 lines (170 loc) · 6.43 KB
/
__main__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import asyncio
import csv
import json
import os
import shutil
import subprocess
import sys
import tempfile
from datetime import date, datetime
from functools import wraps
import click
from . import ClonedVoice, Coqui, Sample
def coroutine(f):
@wraps(f)
def wrapper(*args, **kwargs):
return asyncio.run(f(*args, **kwargs))
return wrapper
def json_serial(obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError("Type %s not serializable" % type(obj))
class PersistedConfig:
def __init__(self, path):
self._path = os.path.expanduser(path)
self._value = self._read()
os.makedirs(os.path.dirname(self._path), exist_ok=True)
def set(self, value):
self._value = value
with open(self._path, "w") as fout:
json.dump(value, fout, indent=2)
def get(self):
return self._value
def _read(self):
if not os.path.exists(self._path):
return None
with open(self._path) as fin:
return json.load(fin)
BASE_URL = None
AuthInfo = PersistedConfig("~/.coqui/credentials")
@click.group()
@click.option("--base-url", default=None)
def main(base_url):
global BASE_URL
BASE_URL = base_url
@main.command()
@click.option("--token", help="API token to sign in with")
@coroutine
async def login(token):
coqui = Coqui(base_url=BASE_URL)
if await coqui.login(token):
AuthInfo.set(token)
click.echo("Logged in!")
else:
click.echo("Error: Invalid token!")
@main.group()
def tts():
pass
@tts.command()
@click.option(
"--fields",
help=f"CSV output, specify which attributes of the available cloned voices to print. Comma separated list, eg: -f id,name. Available fields: {', '.join(ClonedVoice._fields)}",
)
@click.option("--json", "json_out", is_flag=True, help="Print output as JSON")
@coroutine
async def list_voices(fields, json_out):
coqui = Coqui(base_url=BASE_URL)
await coqui.login(AuthInfo.get())
voices = await coqui.cloned_voices()
if json_out:
click.echo(json.dumps([v._asdict() for v in voices], default=json_serial))
elif not fields:
for v in voices:
print(v)
else:
writer = csv.writer(sys.stdout, lineterminator=os.linesep)
for v in voices:
writer.writerow([getattr(v, f) for f in fields.split(",")])
@tts.command()
@click.option("--audio_file", help="Path of reference audio file to clone voice from")
@click.option("--name", help="Name of cloned voice")
@click.option("--json", "json_out", is_flag=True, help="Print output as JSON")
@coroutine
async def clone_voice(audio_file, name, json_out):
coqui = Coqui(base_url=BASE_URL)
await coqui.login(AuthInfo.get())
with open(audio_file, "rb") as fin:
voice = await coqui.clone_voice(fin, name)
if json_out:
click.echo(json.dumps(voice._asdict(), default=json_serial))
else:
click.echo(voice)
@tts.command()
@click.option("--audio_file", help="Path of reference audio file to clone voice from")
@click.option(
"--audio_url", help="URL of reference audio file to estimate quality from"
)
@click.option("--json", "json_out", is_flag=True, help="Print output as JSON")
@coroutine
async def estimate_quality(audio_file, audio_url, json_out):
coqui = Coqui(base_url=BASE_URL)
await coqui.login(AuthInfo.get())
if not audio_file and not audio_url:
raise click.UsageError("Must specify exactly one of: audio_file or audio_url")
quality, raw = await coqui.estimate_quality(
audio_url=audio_url, audio_path=audio_file
)
if json_out:
click.echo(json.dumps({"quality": quality, "raw": raw}, default=json_serial))
else:
click.echo(f"Quality: {quality}\nRaw: {raw}")
@tts.command()
@click.option("--voice", help="ID of voice to list existing samples for")
@click.option(
"--fields",
"-f",
help=f"CSV output, speicfy which attributes of the available samples to print out. Comma separated list, eg: -f id,name. Available fields: {', '.join(Sample._fields)}",
)
@click.option("--json", "json_out", is_flag=True, help="Print output as JSON")
@coroutine
async def list_samples(voice, fields, json_out):
coqui = Coqui(base_url=BASE_URL)
await coqui.login(AuthInfo.get())
samples = await coqui.list_samples(voice_id=voice)
if json_out:
click.echo(json.dumps([s._asdict() for s in samples], default=json_serial))
elif not fields:
click.echo(samples)
else:
writer = csv.writer(sys.stdout, lineterminator=os.linesep)
for s in samples:
writer.writerow([getattr(s, f) for f in fields.split(",")])
@tts.command()
@click.option("--voice", help="ID of voice to synthesize", type=click.UUID)
@click.option("--text", help="Text to synthesize")
@click.option("--speed", help="Speed parameter for synthesis", default=1.0)
@click.option("--name", help="Name of sample", default=None)
@click.option(
"--save",
help="If specified, save the synthesized sample to this file name.",
default=None,
)
@click.option(
"--play",
help="If specified, play the synthesized sample",
is_flag=True,
)
@click.option("--json", "json_out", is_flag=True, help="Print output as JSON")
@coroutine
async def synthesize(voice, text, speed, name, save, play, json_out):
coqui = Coqui(base_url=BASE_URL)
await coqui.login(AuthInfo.get())
sample = await coqui.synthesize(voice, text, speed, name or text[:30])
# sample = {'id': '62151ee3-858f-4398-935d-e48481263927', 'name': 'test from the command line', 'created_at': '2022-06-14T20:15:33.016Z', 'voice_id': 'c97d34da-a677-4219-b4b2-9ec198c948e0', 'audio_url': 'https://coqui-dev-creator-app-synthesized-samples.s3.amazonaws.com/samples/sample_GAh7vFe.wav?AWSAccessKeyId=AKIAXW7NFYT5F2KY3J4D&Signature=CCz46wpRIHrkBT9TCx4vZMVkAQE%3D&Expires=1655241335'}
with tempfile.NamedTemporaryFile("wb") as fout:
await sample.download(fout)
if save:
shutil.copy(fout.name, save)
click.echo(f"Saved synthesized sample to {save}")
elif play:
subprocess.run(
["play", fout.name],
check=True,
stdin=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
elif json_out:
click.echo(json.dumps(sample._asdict(), default=json_serial))
else:
click.echo(sample)