-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnuTTS.nu
More file actions
executable file
·405 lines (393 loc) · 18.6 KB
/
Copy pathnuTTS.nu
File metadata and controls
executable file
·405 lines (393 loc) · 18.6 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
#!/usr/bin/env nu
# Name: nuTTS
# Author: Syretia
# License: MIT
# Dependencies: nushell, phiola
# Description: fetches TTS audio from a couple of free services and returns the result encoded in base64 format or plays using phiola
# fetch and base64 encode audio URLs
def audiourl [voice text] {
# url encode text and prepend url
let enc_url = $text | url encode | prepend $voice | str join ""
# fetch and base64 encode audio url
let base64 = try { http get -m 5sec $enc_url | encode base64 } catch { |e| return ($e.json | from json | wrap error) }
# wrap result for json output
return ($base64 | prepend "data:audio/mp3;base64," | str join "" | wrap audioUrl | merge ($enc_url | wrap originalUrl))
}
# get TikTok TTS audio from weilnet
def weilnet [voice text] {
# setup body json
let body = $text | wrap text | merge ($voice | wrap voice) | to json -r
# make http post request, fallback to weilbyte
let req_json = try {
http post -m 6sec -H [content-type application/json] "https://tiktok-tts.weilnet.workers.dev/api/generation" $body
} catch {
return (weilbyte $voice $text)
}
# prepend audio info and rename column
let json = try {
$req_json | upsert data { |row| $row.data | prepend "data:audio/mp3;base64," | str join ""} | rename -c {data: audioUrl}
} catch {
return (weilbyte $voice $text)
}
# output json result
return $json
}
# get TikTok TTS audio from weilbyte
def weilbyte [voice text] {
# setup body json
let body = $text | wrap text | merge ($voice | wrap voice) | to json -r
# make http post request and bas64 encode result, fallback to cursecode
let base64 = try {
http post -m 6sec -H [content-type application/json] "https://tiktok-tts.weilbyte.dev/api/generate" $body | encode base64
} catch {
return (cursecode $voice $text)
}
# wrap result for json otuput
return ($base64 | prepend "data:audio/mp3;base64," | str join "" | wrap audioUrl)
}
# get TikTok TTS audio from cursecode
def cursecode [voice text] {
# setup body json
let body = $text | wrap text | merge ($voice | wrap voice) | to json -r
# make http post request, fallback to gesserit
let req_json = try {
http post -m 6sec -H [content-type application/json] "https://tts.cursecode.me/api/tts" $body
} catch {
return (gesserit $voice $text)
}
# rename column
let json = try { $req_json | rename -c { audio: audioUrl } } catch { return (gesserit $voice $text) }
# output json result
return $json
}
# get TikTok TTS audio from gesserit
def gesserit [voice text] {
# setup body json
let body = $text | wrap text | merge ($voice | wrap voice) | to json -r
# make http post request, fallback to lazypy
let json = try { http post -m 6sec "https://gesserit.co/api/tiktok-tts" $body } catch { return (lazypy $voice "TikTok" $text) }
# output json result
return $json
}
# get TTS audio from uberduck
def uberduck [voice text] {
# setup body json
let body = $text | wrap text | merge ($voice | wrap voice) | to json -r
# make http post request
let audio_json = try {
http post -m 10sec -H [content-type application/json] "https://www.uberduck.ai/splash-tts" $body
} catch {
|e| return ($e.json | from json | wrap error)
}
let audio_url = try { $audio_json | get response.path } catch { |e| return ($e.json | from json | wrap error) }
# fetch and base64 encode result
let base64 = try { http get -m 5sec ($audio_url) | encode base64 } catch { |e| return ($e.json | from json | wrap error) }
# wrap result for json otuput
return ($base64 | prepend "data:audio/wav;base64," | str join "" | wrap audioUrl | merge $audio_json)
}
# get TTS audio from lazypy
def lazypy [voice service text] {
# create body using url build-query
let body = $text | wrap text | merge ($service | wrap service) | merge ($voice | wrap voice) | url build-query
# post body to lazypy and get audio_url
let audio_json = try {
http post -m 10sec -H [content-type application/x-www-form-urlencoded] "https://lazypy.ro/tts/request_tts.php" $body
} catch {
|e| return ($e.json | from json | wrap error)
}
# return response if success is not true
if ($audio_json | get success) != true {
return ($audio_json | wrap error)
}
let audio_url = try { $audio_json | get audio_url } catch { |e| return ($e.json | from json | wrap error) }
# fetch and base64 encode result
let base64 = try { http get -m 5sec $audio_url | encode base64 } catch { |e| return ($e.json | from json | wrap error) }
return ($base64 | prepend "data:audio/mp3;base64," | str join "" | wrap audioUrl | merge $audio_json)
}
# interactive configuration for nuTTS settings
def config_interactive [] {
# get full TTS voice list
let list = main list | from json
# get list of services
let services = $list | columns
# select service from list
let service = try { $services | input list -f "Select Default TTS Service:" } catch { |e| return ($e.json | from json | wrap error) }
# return if no service selected
if $service == null { return ("No service selected" | wrap error) }
# get list of voices for selected service
let voices = try { $list | get $service | get voices } catch { |e| return ($e.json | from json | wrap error) }
# select voice from list
let voice = try { $voices | input list -f "Select Default TTS Voice:" } catch { |e| return ($e.json | from json | wrap error) }
# return if no voice selected
if $voice == null { return ("No voice selected" | wrap error) }
# get voice ID from selection
let voice = try { $voice | get vid } catch { |e| return ($e.json | from json | wrap error) }
# get list of devices
let devices = try {
phiola device list | complete | get stdout | lines | take until { |r| $r =~ "Capture" } | drop nth 0
} catch {
|e| return ($e.json | from json | wrap error)
}
# select device from list
let device = try { $devices | input list -f "Select Default Playback Device:" } catch { |e| return ($e.json | from json | wrap error) }
# return if no device selected
if $device == null { return ("No device selected" | wrap error) }
# get device number from selection
let device = try { $device | str snake-case | split row "_" | get 0 } catch { |e| return ($e.json | from json | wrap error) }
# setup list of numbers
let numbers = [10,20,30,40,50,60,70,80,90,100]
# select timeout from list
let timeout = try { $numbers | input list -f "Select Default Playback Timeout (in seconds):" } catch { |e| return ($e.json | from json | wrap error) }
# return if no timeout selected
if $timeout == null { return ("No timeout selected" | wrap error) }
# select volume from list
let volume = try { $numbers | input list -f "Select Default Playback Volume:" } catch { |e| return ($e.json | from json | wrap error) }
# return if no volume selected
if $volume == null { return ("No volume selected" | wrap error) }
# setup list of durations
let waits = [0sec,5sec,10sec,15sec,20sec,25sec,30sec]
# select wait from list
let wait = try { $waits | input list -f "Select Default Time to Wait Before Playback:" } catch { |e| return ($e.json | from json | wrap error) }
# return if no wait selected
if $wait == null { return ("No wait selected" | wrap error) }
# update data in stor
try {
$service | wrap service
| merge ($voice | wrap voice)
| merge ($device | wrap device)
| merge ($timeout | wrap timeout)
| merge ($volume | wrap volume)
| merge ($wait | wrap wait)
| stor update --table-name nuTTS
} catch {
|e| return ($e.json | from json | wrap error)
}
# export stor to file
try {
mv -f $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite.bak"
} catch {
|e| return ($e.json | from json | wrap error)
}
try {
stor export --file-name $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite"
} catch {
|e| mv -f $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite.bak" $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite"
return ($e.json | from json | wrap error)
}
# output config and delete table from memory
let output = try { stor open | $in.nuTTS.0 } catch { |e| return ($e.json | from json | wrap error) }
try { stor delete --table-name nuTTS } catch { |e| return ($e.json | from json | wrap error) }
return $output
}
# Configures default settings for nuTTS
#
# If no flags are used, interactive configuration will be ran
# Results are output in JSON format unless ran in interactive mode
def "main config" [
--service (-s):string # set default TTS service (must be used with --voice flag)
--voice (-v):string # set default TTS voice (must be used with --service flag)
--device (-d):int # set default playback device for 'play' subcommand
--timeout (-t):int # set default playback timeout for 'play' subcommand
--volume (-V):int # set default playback volume for 'play' subcommand
--wait (-w):duration # set default time to wait before playback for 'play' subcommand
]: nothing -> string {
# check if config file exists
if ($"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" | path type) == "file" {
# import config into stor if exists
try { stor import --file-name $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" } catch { |e| return ($e.json | from json | wrap error | to json) }
} else {
# config file doesn't exist, create nuTTS config dir
try { mkdir $"($nu.default-config-dir)/../nuTTS" } catch { |e| return ($e.json | from json | wrap error | to json) }
# create stor
try {
stor create --table-name nuTTS --columns {service: str, voice: str, device: int, timeout: int, volume: int, wait: str}
} catch {
|e| return ($e.json | from json | wrap error | to json)
}
# insert default data into stor
try {
"Streamlabs" | wrap service
| merge ("Brian" | wrap voice)
| merge (0 | wrap device)
| merge (60 | wrap timeout)
| merge (100 | wrap volume)
| merge ("0sec" | wrap wait)
| stor insert --table-name nuTTS
} catch {
|e| return ($e.json | from json | wrap error | to json)
}
# export stor to file
try { stor export --file-name $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" } catch { |e| return ($e.json | from json | wrap error | to json) }
}
# run interactive config if no flags used
if $service == null and $voice == null and $device == null and $timeout == null and $volume == null and $wait == null {
let output = try { config_interactive } catch { |e| return ($e.json | from json | wrap error) }
return $output
}
# get full TTS voice list
let list = main list | from json
# set service if not null
if $service != null and $voice != null {
# get list of services
let services = $list | columns
# check if input is in list of services
if $service in $services {
try { $service | wrap service | stor update --table-name nuTTS } catch { |e| return ($e.json | from json | wrap error | to json) }
} else {
return ("Invalid service input" | wrap error | to json)
}
}
# set voice if not null
if $service != null and $voice != null {
# get default service from stor
let service = try { stor open | to json | from json | get nuTTS.0.service } catch { |e| return ($e.json | from json | wrap error | to json) }
# get list of voices for service
let voices = try { $list | get $service | get voices } catch { |e| return ($e.json | from json | wrap error | to json) }
# check if input is in list of voices for service
if $voice in ($voices | get vid) {
# update stor
try { $voice | wrap voice | stor update --table-name nuTTS } catch { |e| return ($e.json | from json | wrap error | to json) }
} else {
return ("Invalid voice input" | wrap error | to json)
}
}
# set device if not null
if $device != null {
# update stor
try { $device | wrap device | stor update --table-name nuTTS } catch { |e| return ($e.json | from json | wrap error | to json) }
}
# set timeout if not null
if $timeout != null {
# update stor
try { $timeout | wrap timeout | stor update --table-name nuTTS } catch { |e| return ($e.json | from json | wrap error | to json) }
}
# set volume if not null
if $volume != null {
# update stor
try { $volume | wrap volume | stor update --table-name nuTTS } catch { |e| return ($e.json | from json | wrap error | to json) }
}
# set wait if not null
if $wait != null {
# update stor
try { $wait | into string | wrap wait | stor update --table-name nuTTS } catch { |e| return ($e.json | from json | wrap error | to json) }
}
# export stor to file
try {
mv -f $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite.bak"
} catch {
|e| return ($e.json | from json | wrap error | to json)
}
try {
stor export --file-name $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite"
} catch {
|e| mv -f $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite.bak" $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite"
return ($e.json | from json | wrap error | to json)
}
# output config in JSON format and delete table from memory
let output = try { stor open | $in.nuTTS.0 | to json } catch { |e| return ($e.json | from json | wrap error | to json) }
try { stor delete --table-name nuTTS } catch { |e| return ($e.json | from json | wrap error | to json) }
return $output
}
# Gets JSON list of TTS voices
def "main list" [
--service (-s):string # get voices for specific service
]: nothing -> string {
# if no --service input, list all services from tts_list.json
if $service == null {
let list = try {
http get -r "https://raw.githubusercontent.com/simoniz0r/nuTTS/main/tts_list.json"
} catch {
|e| return ($e.json | from json | wrap error | to json)
}
return $list
# else get list for --service input from tts_list.json
} else {
let list = try {
http get "https://raw.githubusercontent.com/simoniz0r/nuTTS/main/tts_list.json" | get $service | to json
} catch {
|e| return ($e.json | from json | wrap error | to json)
}
return $list
}
}
# Plays TTS audio using phiola, returns playback status
#
# Results are output in JSON format by default
def "main play" [
text:string # text to speek
--service (-s):string # service to use for TTS voice (default: Streamlabs)
--voice (-v):string # voice ID for TTS (default: Brian)
--device (-d):int # device number for TTS playback; 0 is system default (default: 0)
--exit-code (-e) # output phiola exit code instead of JSON (default: JSON)
--timeout (-t):int # max playback seconds for TTS (default: 60)
--volume (-V):int # playback volume for TTS (0-100) (default: 100)
--wait (-w):duration # seconds to wait before starting TTS playback (default: 0sec)
]: nothing -> string {
# check if config file exists
let settings = if ($"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" | path type) == "file" {
# get settings if nuTTS.sqlite exists
try { open $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" | get nuTTS.0 } catch { |e| return ($e.json | from json | wrap error | to json) }
}
# get settings from default if no inputs
let voice = if $voice == null { $settings.voice? | default "Brian" } else { $voice }
let service = if $service == null { $settings.service? | default "Streamlabs" } else { $service }
let device = if $device == null { $settings.device? | default 0 } else { $device }
let timeout = if $timeout == null { $settings.timeout? | default 60 } else { $timeout }
let volume = if $volume == null { $settings.volume? | default 100 } else { $volume }
let wait = if $wait == null { $settings.wait? | default 0sec | into duration } else { $wait }
# decode text
let detext = $text | url decode
# route based on service
let result = match ($service | str downcase) {
audiourl => { audiourl $voice $detext },
tiktok => { weilnet $voice $detext },
uberduck => { uberduck $voice $detext },
_ => { lazypy $voice $service $detext }
}
# get base64 from result
let base64 = try { $result | get audioUrl | split row "base64," | get 1 } catch { return ($result | to json) }
# sleep for $wait before playing
sleep $wait
# decode base64 and play with phiola
let playback = try {
$base64 | decode base64 | phiola play -device $device -until $timeout -volume $volume @stdin | complete
} catch {
|e| return ($e.json | from json | wrap error | to json)
}
# return phiola result as json if $exit_code is false
if $exit_code == false {
return ($playback | to json)
# else output only exit code
} else {
let phiola_exit = try { $playback | get exit_code } catch { |e| return ($e.json | from json | wrap error | to json) }
return $phiola_exit
}
}
# Gets TTS audio for given service, returns base64 encoded audio
#
# Results are output in JSON format
def main [
text:string # text to speek
--service (-s):string # service to use for TTS voice (default: Streamlabs)
--voice (-v):string # voice ID for TTS (default: Brian)
]: nothing -> string {
# check if config file exists
let settings = if ($"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" | path type) == "file" {
# get settings if nuTTS.sqlite exists
try { open $"($nu.default-config-dir)/../nuTTS/nuTTS.sqlite" | get nuTTS.0 } catch { |e| return ($e.json | from json | wrap error | to json) }
}
# get settings from default if no inputs
let voice = if $voice == null { $settings.voice? | default "Brian" } else { $voice }
let service = if $service == null { $settings.service? | default "Streamlabs" } else { $service }
# decode text
let detext = $text | url decode
# route based on service
let result = match ($service | str downcase) {
audiourl => { audiourl $voice $detext },
tiktok => { weilnet $voice $detext },
uberduck => { uberduck $voice $detext },
_ => { lazypy $voice $service $detext }
}
# return result in json format
return ($result | to json)
}