@voxygen/voxygen-tts 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +26 -0
- package/voxygen-tts/client.mjs +131 -0
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voxygen/voxygen-tts",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tts"
|
|
7
|
+
],
|
|
8
|
+
"homepage": "https://github.com/katell-qmnr-voxygen/voxygen-js-client-test#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/katell-qmnr-voxygen/voxygen-js-client-test/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/katell-qmnr-voxygen/voxygen-js-client-test.git"
|
|
15
|
+
},
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"author": "",
|
|
18
|
+
"type": "commonjs",
|
|
19
|
+
"main": "voxygen-tts/client.mjs",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "restricted"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export default class SyntHTTPClient {
|
|
4
|
+
static MimeType = Object.freeze({
|
|
5
|
+
PLAIN_TEXT: Symbol("PLAIN_TEXT"),
|
|
6
|
+
AUDIO: Symbol("AUDIO"),
|
|
7
|
+
JSON: Symbol("JSON"),
|
|
8
|
+
URL_ENCODED: Symbol("URL_ENCODED"),
|
|
9
|
+
MULTIPART: Symbol("MULTIPART")
|
|
10
|
+
})
|
|
11
|
+
#token
|
|
12
|
+
#url
|
|
13
|
+
#max_retries
|
|
14
|
+
#retry_after
|
|
15
|
+
#body_type
|
|
16
|
+
#accept_type
|
|
17
|
+
constructor(token, url = 'https://api.voxygen.fr/tts') {
|
|
18
|
+
if (typeof token !== 'string' || token === '')
|
|
19
|
+
throw new Error("token must be provided");
|
|
20
|
+
this.#token = token;
|
|
21
|
+
this.#url = new URL(url);
|
|
22
|
+
// default retry policy : 6 times with a 10 second wait
|
|
23
|
+
this.setRetryPolicy(6, 10 /* seconds */);
|
|
24
|
+
// default request content type : JSON
|
|
25
|
+
this.setRequestContentType(SyntHTTPClient.MimeType.JSON);
|
|
26
|
+
// default accept content type : AUDIO
|
|
27
|
+
this.setAcceptContentType(SyntHTTPClient.MimeType.AUDIO);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setRetryPolicy(max_retries, retry_after) {
|
|
31
|
+
this.#max_retries = max_retries;
|
|
32
|
+
this.#retry_after = retry_after;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setRequestContentType(mime_type) {
|
|
36
|
+
this.#body_type = mime_type;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setAcceptContentType(mime_type) {
|
|
40
|
+
this.#accept_type = mime_type;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async buildRequest(anObject) {
|
|
44
|
+
let request = {};
|
|
45
|
+
// initialize query from host url
|
|
46
|
+
this.#url.searchParams.forEach((value, key) => request[key] = value);
|
|
47
|
+
this.#url.search = ""; // empty URL search, since it's now in the request body
|
|
48
|
+
// add arguments to request query (argument values take priority over existing parameters)
|
|
49
|
+
for (const [key, value] of Object.entries(anObject)) {
|
|
50
|
+
request[key] = value;
|
|
51
|
+
}
|
|
52
|
+
// NOTE: rfc2046 section-4.1.1 "MUST always represent a line break as a CRLF sequence"
|
|
53
|
+
for (const [key, value] of Object.entries(request)) {
|
|
54
|
+
if (typeof value === 'string')
|
|
55
|
+
request[key] = value.replace(/(\r?\n)/g, '\r\n');
|
|
56
|
+
}
|
|
57
|
+
return request;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#sleep = (sec) => new Promise((resolve) => setTimeout(() => resolve(), sec * 1000))
|
|
61
|
+
|
|
62
|
+
fetch(aRequest, options = {}) {
|
|
63
|
+
options.method = "POST";
|
|
64
|
+
if (!options.headers)
|
|
65
|
+
options.headers = new Headers();
|
|
66
|
+
options.headers.append("User-Agent", "Voxygen-Client/1.3 (javascript)");
|
|
67
|
+
let aBody;
|
|
68
|
+
switch (this.#body_type) {
|
|
69
|
+
case SyntHTTPClient.MimeType.JSON:
|
|
70
|
+
aBody = JSON.stringify(aRequest);
|
|
71
|
+
// NOTE: rfc8259 section-8.1 "JSON text exchanged between systems that are not part of a closed ecosystem MUST be encoded using UTF-8"
|
|
72
|
+
options.headers.append("Content-Type", "application/json");
|
|
73
|
+
break;
|
|
74
|
+
case SyntHTTPClient.MimeType.MULTIPART:
|
|
75
|
+
aBody = new FormData(); // fetch() will produce multipart/form-data Content-Type from a FormData body
|
|
76
|
+
for (const [key, value] of Object.entries(aRequest)) {
|
|
77
|
+
aBody.append(key, value);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
case SyntHTTPClient.MimeType.URL_ENCODED:
|
|
81
|
+
aBody = new URLSearchParams(); // fetch() will produce application/x-www-form-urlencoded Content-Type from a URLSearchParams body
|
|
82
|
+
for (const [key, value] of Object.entries(aRequest)) {
|
|
83
|
+
aBody.append(key, value);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`unsupported body type`);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
switch (this.#accept_type) {
|
|
91
|
+
case SyntHTTPClient.MimeType.AUDIO:
|
|
92
|
+
options.headers.append("Accept", "audio/*; q=1.0, application/octet-stream; q=0.8, */*; q=0.1");
|
|
93
|
+
break;
|
|
94
|
+
case SyntHTTPClient.MimeType.JSON:
|
|
95
|
+
options.headers.append("Accept", "application/json, */*; q=0.1");
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
throw new Error(`unsupported accept content type`);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
options.headers.append("Authorization", `Bearer ${this.#token}`);
|
|
102
|
+
options.body = aBody;
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const wrapper = (n) => {
|
|
105
|
+
fetch(this.#url.toString(), options)
|
|
106
|
+
.then((response) => {
|
|
107
|
+
if (response.status == 503 /* Service Unavailable */ && n) {
|
|
108
|
+
this.#sleep(this.#retry_after).then(() => wrapper(--n));
|
|
109
|
+
} else {
|
|
110
|
+
resolve(response);
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
.catch(async (err) => reject(err))
|
|
114
|
+
};
|
|
115
|
+
wrapper(this.#max_retries);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getContentType(aResponse) {
|
|
120
|
+
const contentType = aResponse.headers.get("Content-Type");
|
|
121
|
+
if (contentType != null) {
|
|
122
|
+
if (contentType.startsWith("audio/") || contentType === 'application/octet-stream') {
|
|
123
|
+
return SyntHTTPClient.MimeType.AUDIO;
|
|
124
|
+
} else if (contentType === 'application/json') {
|
|
125
|
+
return SyntHTTPClient.MimeType.JSON;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return SyntHTTPClient.MimeType.PLAIN_TEXT;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
}
|