@fonoster/apiserver 0.7.22 → 0.7.23
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/dist/core/filesServer.d.ts +6 -2
- package/dist/core/filesServer.js +31 -35
- package/dist/core/runServices.js +1 -2
- package/dist/envs.d.ts +0 -1
- package/dist/envs.js +1 -3
- package/dist/voice/VoiceClientImpl.d.ts +2 -2
- package/dist/voice/VoiceClientImpl.js +8 -8
- package/dist/voice/VoiceDispatcher.js +3 -3
- package/dist/voice/connectToAri.d.ts +1 -1
- package/dist/voice/connectToAri.js +5 -5
- package/dist/voice/{ChannelVarNotFoundError.d.ts → errors/ChannelVarNotFoundError.d.ts} +1 -1
- package/dist/voice/errors/index.d.ts +2 -0
- package/dist/voice/errors/index.js +36 -0
- package/dist/voice/handlers/Say.js +2 -2
- package/dist/voice/handlers/dial/Dial.js +1 -1
- package/dist/voice/integrations/getTtsConfig.d.ts +0 -1
- package/dist/voice/integrations/getTtsConfig.js +1 -3
- package/dist/voice/makeCreateVoiceClient.d.ts +1 -1
- package/dist/voice/makeCreateVoiceClient.js +4 -4
- package/dist/voice/stt/AbstractSpeechToText.js +1 -1
- package/dist/voice/tts/AbstractTextToSpeech.d.ts +8 -9
- package/dist/voice/tts/AbstractTextToSpeech.js +5 -67
- package/dist/voice/tts/Azure.d.ts +7 -5
- package/dist/voice/tts/Azure.js +9 -12
- package/dist/voice/tts/Deepgram.d.ts +7 -3
- package/dist/voice/tts/Deepgram.js +4 -44
- package/dist/voice/tts/ElevenLabs.d.ts +7 -3
- package/dist/voice/tts/ElevenLabs.js +5 -33
- package/dist/voice/tts/Google.d.ts +7 -3
- package/dist/voice/tts/Google.js +6 -12
- package/dist/voice/tts/TextToSpeechFactory.d.ts +3 -4
- package/dist/voice/tts/types.d.ts +1 -4
- package/dist/voice/types/voice.d.ts +5 -4
- package/dist/voice/utils/VoiceServiceClientConstructor.d.ts +3 -0
- package/dist/voice/{VoiceServiceClientConst.js → utils/VoiceServiceClientConstructor.js} +3 -3
- package/dist/voice/{createExternalMediaConfig.js → utils/createExternalMediaConfig.js} +1 -1
- package/dist/voice/utils/index.d.ts +4 -0
- package/dist/voice/utils/index.js +38 -0
- package/dist/voice/utils/makeGetChannelVar.d.ts +4 -0
- package/dist/voice/{makeGetChannelVar.js → utils/makeGetChannelVar.js} +1 -14
- package/dist/voice/utils/makeGetChannelVarWithoutThrow.d.ts +4 -0
- package/dist/voice/utils/makeGetChannelVarWithoutThrow.js +24 -0
- package/package.json +2 -2
- package/dist/voice/VoiceServiceClientConst.d.ts +0 -3
- package/dist/voice/makeGetChannelVar.d.ts +0 -5
- package/dist/voice/tts/computeFilename.d.ts +0 -7
- package/dist/voice/tts/computeFilename.js +0 -38
- package/dist/voice/tts/flattenObject.d.ts +0 -2
- package/dist/voice/tts/flattenObject.js +0 -38
- /package/dist/voice/{ChannelVarNotFoundError.js → errors/ChannelVarNotFoundError.js} +0 -0
- /package/dist/voice/{MethodNotImplementedError.d.ts → errors/MethodNotImplementedError.d.ts} +0 -0
- /package/dist/voice/{MethodNotImplementedError.js → errors/MethodNotImplementedError.js} +0 -0
- /package/dist/voice/{createExternalMediaConfig.d.ts → utils/createExternalMediaConfig.d.ts} +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { Readable } from "stream";
|
|
1
2
|
declare function filesServer(params: {
|
|
2
|
-
pathToFiles: string;
|
|
3
3
|
port: number;
|
|
4
|
-
}):
|
|
4
|
+
}): {
|
|
5
|
+
addStream: (id: string, stream: Readable) => void;
|
|
6
|
+
removeStream: (id: string) => void;
|
|
7
|
+
getStream: (id: string) => Readable;
|
|
8
|
+
};
|
|
5
9
|
export { filesServer };
|
package/dist/core/filesServer.js
CHANGED
|
@@ -4,50 +4,46 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.filesServer = filesServer;
|
|
7
|
-
/*
|
|
8
|
-
* Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
|
|
9
|
-
* http://github.com/fonoster/fonoster
|
|
10
|
-
*
|
|
11
|
-
* This file is part of Fonoster
|
|
12
|
-
*
|
|
13
|
-
* Licensed under the MIT License (the "License");
|
|
14
|
-
* you may not use this file except in compliance with
|
|
15
|
-
* the License. You may obtain a copy of the License at
|
|
16
|
-
*
|
|
17
|
-
* https://opensource.org/licenses/MIT
|
|
18
|
-
*
|
|
19
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
20
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
21
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
22
|
-
* See the License for the specific language governing permissions and
|
|
23
|
-
* limitations under the License.
|
|
24
|
-
*/
|
|
25
|
-
const fs_1 = __importDefault(require("fs"));
|
|
26
|
-
const path_1 = require("path");
|
|
27
7
|
const logger_1 = require("@fonoster/logger");
|
|
28
8
|
const express_1 = __importDefault(require("express"));
|
|
29
9
|
const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
|
|
30
10
|
const CONTENT_TYPE = "audio/L16;rate=16000;channels=1";
|
|
31
11
|
function filesServer(params) {
|
|
32
|
-
const {
|
|
12
|
+
const { port } = params;
|
|
33
13
|
const app = (0, express_1.default)();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
readStream.pipe(res);
|
|
14
|
+
const streamMap = new Map();
|
|
15
|
+
app.get("/sounds/:id", (req, res) => {
|
|
16
|
+
const idWithoutExtension = req.params.id.split(".")[0];
|
|
17
|
+
const stream = streamMap.get(idWithoutExtension);
|
|
18
|
+
if (!stream) {
|
|
19
|
+
res.status(404).send(`Stream not found for id: ${req.params.id}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
res.setHeader("content-type", CONTENT_TYPE);
|
|
23
|
+
stream.on("error", (error) => {
|
|
24
|
+
logger.error(`Error reading file: ${error.message}`);
|
|
25
|
+
res.status(500).send("Error reading file!");
|
|
48
26
|
});
|
|
27
|
+
stream.on("end", () => {
|
|
28
|
+
res.end();
|
|
29
|
+
});
|
|
30
|
+
stream.on("close", () => {
|
|
31
|
+
res.end();
|
|
32
|
+
});
|
|
33
|
+
stream.pipe(res);
|
|
49
34
|
});
|
|
50
35
|
app.listen(port, () => {
|
|
51
36
|
logger.info(`Files server is running on port ${port}`);
|
|
52
37
|
});
|
|
38
|
+
return {
|
|
39
|
+
addStream: (id, stream) => {
|
|
40
|
+
streamMap.set(id, stream);
|
|
41
|
+
},
|
|
42
|
+
removeStream: (id) => {
|
|
43
|
+
streamMap.delete(id);
|
|
44
|
+
},
|
|
45
|
+
getStream: (id) => {
|
|
46
|
+
return streamMap.get(id);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
53
49
|
}
|
package/dist/core/runServices.js
CHANGED
|
@@ -79,7 +79,7 @@ function runServices() {
|
|
|
79
79
|
// Load the remaining services
|
|
80
80
|
(0, loadServices_1.default)(server, yield services_1.default);
|
|
81
81
|
// Connecting to Asterisk ARI
|
|
82
|
-
yield (0, connectToAri_1.connectToAri)();
|
|
82
|
+
yield (0, connectToAri_1.connectToAri)((0, filesServer_1.filesServer)({ port: envs_1.FILES_SERVER_PORT }));
|
|
83
83
|
// Additional Call Managers subscriber may be added here to handle call events
|
|
84
84
|
yield (0, runCallManager_1.createCreateCallSubscriber)({
|
|
85
85
|
natsUrl: envs_1.NATS_URL,
|
|
@@ -87,7 +87,6 @@ function runServices() {
|
|
|
87
87
|
ariUsername: envs_1.ASTERISK_ARI_USERNAME,
|
|
88
88
|
ariPassword: envs_1.ASTERISK_ARI_SECRET
|
|
89
89
|
});
|
|
90
|
-
(0, filesServer_1.filesServer)({ pathToFiles: envs_1.TTS_PATH_TO_FILES, port: envs_1.FILES_SERVER_PORT });
|
|
91
90
|
server.bindAsync(envs_1.APISERVER_BIND_ADDR, credentials, () => __awaiter(this, void 0, void 0, function* () {
|
|
92
91
|
healthImpl.setStatus("", common_1.GRPC_SERVING_STATUS);
|
|
93
92
|
logger.info(`apiserver running at ${envs_1.APISERVER_BIND_ADDR}`);
|
package/dist/envs.d.ts
CHANGED
package/dist/envs.js
CHANGED
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
var _a;
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.
|
|
7
|
+
exports.SMTP_SENDER = exports.SMTP_SECURE = exports.SMTP_PORT = exports.SMTP_HOST = exports.SMTP_AUTH_USER = exports.SMTP_AUTH_PASS = exports.ROUTR_DEFAULT_PEER_USERNAME = exports.ROUTR_DEFAULT_PEER_PASSWORD = exports.ROUTR_DEFAULT_PEER_NAME = exports.ROUTR_DEFAULT_PEER_AOR = exports.ROUTR_API_ENDPOINT = exports.OWNER_PASSWORD = exports.OWNER_NAME = exports.OWNER_EMAIL = exports.NATS_URL = exports.INTEGRATIONS_FILE = exports.INFLUXDB_USERNAME = exports.INFLUXDB_URL = exports.INFLUXDB_TOKEN = exports.INFLUXDB_PASSWORD = exports.INFLUXDB_ORG = exports.INFLUXDB_BUCKET = exports.IDENTITY_REFRESH_TOKEN_EXPIRES_IN = exports.IDENTITY_PUBLIC_KEY = exports.IDENTITY_PRIVATE_KEY = exports.IDENTITY_ISSUER = exports.IDENTITY_ID_TOKEN_EXPIRES_IN = exports.IDENTITY_AUDIENCE = exports.IDENTITY_ACCESS_TOKEN_EXPIRES_IN = exports.FILES_SERVER_PORT = exports.EMAIL_TEMPLATES_DIR = exports.DEFAULT_NATS_QUEUE_GROUP = exports.CLOAK_ENCRYPTION_KEY = exports.CALLS_TRACK_CALL_SUBJECT = exports.CALLS_CREATE_SUBJECT = exports.ASTERISK_TRUNK = exports.ASTERISK_SYSTEM_DOMAIN = exports.ASTERISK_ARI_USERNAME = exports.ASTERISK_ARI_SECRET = exports.ASTERISK_ARI_PROXY_URL = exports.APP_URL = exports.APISERVER_HOST = exports.APISERVER_BIND_ADDR = void 0;
|
|
8
8
|
/*
|
|
9
9
|
* Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
|
|
10
10
|
* http://github.com/fonoster/fonoster
|
|
@@ -24,7 +24,6 @@ exports.TTS_PATH_TO_FILES = exports.SMTP_SENDER = exports.SMTP_SECURE = exports.
|
|
|
24
24
|
* limitations under the License.
|
|
25
25
|
*/
|
|
26
26
|
const fs_1 = __importDefault(require("fs"));
|
|
27
|
-
const os_1 = __importDefault(require("os"));
|
|
28
27
|
const path_1 = require("path");
|
|
29
28
|
const common_1 = require("@fonoster/common");
|
|
30
29
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
@@ -108,4 +107,3 @@ exports.SMTP_HOST = e.SMTP_HOST;
|
|
|
108
107
|
exports.SMTP_PORT = e.SMTP_PORT ? parseInt(e.SMTP_PORT) : 587;
|
|
109
108
|
exports.SMTP_SECURE = ((_a = e.SMTP_SECURE) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "true";
|
|
110
109
|
exports.SMTP_SENDER = e.SMTP_SENDER;
|
|
111
|
-
exports.TTS_PATH_TO_FILES = e.TTS_PATH_TO_FILES || os_1.default.tmpdir();
|
|
@@ -16,17 +16,17 @@ declare class VoiceClientImpl implements VoiceClient {
|
|
|
16
16
|
asStream: Stream;
|
|
17
17
|
ari: Client;
|
|
18
18
|
bridge: Bridge;
|
|
19
|
+
filesServer: any;
|
|
19
20
|
constructor(params: {
|
|
20
21
|
ari: Client;
|
|
21
22
|
config: VoiceClientConfig;
|
|
22
23
|
tts: TextToSpeech;
|
|
23
24
|
stt: SpeechToText;
|
|
24
|
-
});
|
|
25
|
+
}, filesServer: any);
|
|
25
26
|
connect(): Promise<void>;
|
|
26
27
|
setupAudioSocket(port: number): void;
|
|
27
28
|
on(type: string, callback: (data: VoiceIn) => void): void;
|
|
28
29
|
sendResponse(response: VoiceIn): void;
|
|
29
|
-
getBridge(): Bridge;
|
|
30
30
|
getTranscriptionsStream(): Stream;
|
|
31
31
|
setupExternalMedia(port: number): Promise<void>;
|
|
32
32
|
synthesize(text: string, options: SayOptions): Promise<string>;
|
|
@@ -58,13 +58,13 @@ const logger_1 = require("@fonoster/logger");
|
|
|
58
58
|
const streams_1 = require("@fonoster/streams");
|
|
59
59
|
const grpc = __importStar(require("@grpc/grpc-js"));
|
|
60
60
|
const pick_port_1 = require("pick-port");
|
|
61
|
-
const createExternalMediaConfig_1 = require("./createExternalMediaConfig");
|
|
62
61
|
const transcribeOnConnection_1 = require("./transcribeOnConnection");
|
|
63
62
|
const types_1 = require("./types");
|
|
64
|
-
const
|
|
63
|
+
const createExternalMediaConfig_1 = require("./utils/createExternalMediaConfig");
|
|
64
|
+
const VoiceServiceClientConstructor_1 = require("./utils/VoiceServiceClientConstructor");
|
|
65
65
|
const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
|
|
66
66
|
class VoiceClientImpl {
|
|
67
|
-
constructor(params) {
|
|
67
|
+
constructor(params, filesServer) {
|
|
68
68
|
const { config, tts, stt, ari } = params;
|
|
69
69
|
this.config = config;
|
|
70
70
|
this.verbsStream = new stream_1.Stream();
|
|
@@ -72,10 +72,11 @@ class VoiceClientImpl {
|
|
|
72
72
|
this.tts = tts;
|
|
73
73
|
this.stt = stt;
|
|
74
74
|
this.ari = ari;
|
|
75
|
+
this.filesServer = filesServer;
|
|
75
76
|
}
|
|
76
77
|
connect() {
|
|
77
78
|
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
-
this.grpcClient = new
|
|
79
|
+
this.grpcClient = new VoiceServiceClientConstructor_1.VoiceServiceClientConstructor(this.config.endpoint, grpc.credentials.createInsecure());
|
|
79
80
|
const metadata = new grpc.Metadata();
|
|
80
81
|
metadata.add("accessKeyId", this.config.accessKeyId);
|
|
81
82
|
metadata.add("token", this.config.sessionToken);
|
|
@@ -115,9 +116,6 @@ class VoiceClientImpl {
|
|
|
115
116
|
sendResponse(response) {
|
|
116
117
|
this.voice.write(response);
|
|
117
118
|
}
|
|
118
|
-
getBridge() {
|
|
119
|
-
return this.bridge;
|
|
120
|
-
}
|
|
121
119
|
getTranscriptionsStream() {
|
|
122
120
|
return this.transcriptionsStream;
|
|
123
121
|
}
|
|
@@ -150,7 +148,9 @@ class VoiceClientImpl {
|
|
|
150
148
|
}
|
|
151
149
|
synthesize(text, options) {
|
|
152
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
-
|
|
151
|
+
const { ref, stream } = yield this.tts.synthesize(text, options);
|
|
152
|
+
this.filesServer.addStream(ref, stream);
|
|
153
|
+
return ref;
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
156
|
transcribe() {
|
|
@@ -32,8 +32,8 @@ const common_1 = require("@fonoster/common");
|
|
|
32
32
|
const logger_1 = require("@fonoster/logger");
|
|
33
33
|
const handlers_1 = require("./handlers");
|
|
34
34
|
const Stream_1 = require("./handlers/Stream");
|
|
35
|
-
const makeGetChannelVar_1 = require("./makeGetChannelVar");
|
|
36
35
|
const types_1 = require("./types");
|
|
36
|
+
const makeGetChannelVarWithoutThrow_1 = require("./utils/makeGetChannelVarWithoutThrow");
|
|
37
37
|
const utils_1 = require("../utils");
|
|
38
38
|
const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
|
|
39
39
|
class VoiceDispatcher {
|
|
@@ -55,7 +55,7 @@ class VoiceDispatcher {
|
|
|
55
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
56
56
|
var _a;
|
|
57
57
|
const { ari, voiceClients, createVoiceClient, isHandledElsewhere } = this;
|
|
58
|
-
const getChannelVar = (0,
|
|
58
|
+
const getChannelVar = (0, makeGetChannelVarWithoutThrow_1.makeGetChannelVarWithoutThrow)(channel);
|
|
59
59
|
const appRef = (_a = (yield getChannelVar(types_1.ChannelVar.APP_REF))) === null || _a === void 0 ? void 0 : _a.value;
|
|
60
60
|
// This check feels strange but is necessary as ARI calls this event twice
|
|
61
61
|
if (!appRef) {
|
|
@@ -107,7 +107,7 @@ class VoiceDispatcher {
|
|
|
107
107
|
isHandledElsewhere(channel) {
|
|
108
108
|
return __awaiter(this, void 0, void 0, function* () {
|
|
109
109
|
var _a;
|
|
110
|
-
return (((_a = (yield (0,
|
|
110
|
+
return (((_a = (yield (0, makeGetChannelVarWithoutThrow_1.makeGetChannelVarWithoutThrow)(channel)(types_1.ChannelVar.FROM_EXTERNAL_MEDIA))) === null || _a === void 0 ? void 0 : _a.value) === "true");
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare function connectToAri(): Promise<void>;
|
|
1
|
+
declare function connectToAri(filesServer: any): Promise<void>;
|
|
2
2
|
export { connectToAri };
|
|
@@ -48,7 +48,7 @@ const connection = {
|
|
|
48
48
|
timeout: 5000,
|
|
49
49
|
output: "silent"
|
|
50
50
|
};
|
|
51
|
-
function connectToAri() {
|
|
51
|
+
function connectToAri(filesServer) {
|
|
52
52
|
return __awaiter(this, void 0, void 0, function* () {
|
|
53
53
|
logger.info("waiting for asterisk server");
|
|
54
54
|
const open = yield (0, wait_port_1.default)(connection);
|
|
@@ -59,12 +59,12 @@ function connectToAri() {
|
|
|
59
59
|
});
|
|
60
60
|
ari.on(types_1.AriEvent.WEB_SOCKET_MAX_RETRIES, () => {
|
|
61
61
|
logger.error("max retries reconnecting to asterisk");
|
|
62
|
-
attemptReconnection();
|
|
62
|
+
attemptReconnection(filesServer);
|
|
63
63
|
});
|
|
64
64
|
logger.info("asterisk is ready");
|
|
65
65
|
const createContainer = (0, integrations_1.makeCreateContainer)(db_1.prisma, envs_1.INTEGRATIONS_FILE);
|
|
66
66
|
const nats = yield (0, nats_1.connect)({ servers: envs_1.NATS_URL });
|
|
67
|
-
const dispatcher = new VoiceDispatcher_1.VoiceDispatcher(ari, nats, (0, makeCreateVoiceClient_1.makeCreateVoiceClient)(createContainer));
|
|
67
|
+
const dispatcher = new VoiceDispatcher_1.VoiceDispatcher(ari, nats, (0, makeCreateVoiceClient_1.makeCreateVoiceClient)(createContainer, filesServer));
|
|
68
68
|
dispatcher.start();
|
|
69
69
|
}
|
|
70
70
|
else {
|
|
@@ -73,9 +73,9 @@ function connectToAri() {
|
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
-
function attemptReconnection() {
|
|
76
|
+
function attemptReconnection(filesServer) {
|
|
77
77
|
logger.info("attempting to reconnect in 5 seconds...");
|
|
78
78
|
setTimeout(() => {
|
|
79
|
-
connectToAri();
|
|
79
|
+
connectToAri(filesServer);
|
|
80
80
|
}, 5000); // Reconnect after 5 seconds
|
|
81
81
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/*
|
|
18
|
+
* Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
|
|
19
|
+
* http://github.com/fonoster/fonoster
|
|
20
|
+
*
|
|
21
|
+
* This file is part of Fonoster
|
|
22
|
+
*
|
|
23
|
+
* Licensed under the MIT License (the "License");
|
|
24
|
+
* you may not use this file except in compliance with
|
|
25
|
+
* the License. You may obtain a copy of the License at
|
|
26
|
+
*
|
|
27
|
+
* https://opensource.org/licenses/MIT
|
|
28
|
+
*
|
|
29
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
30
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
31
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
32
|
+
* See the License for the specific language governing permissions and
|
|
33
|
+
* limitations under the License.
|
|
34
|
+
*/
|
|
35
|
+
__exportStar(require("./ChannelVarNotFoundError"), exports);
|
|
36
|
+
__exportStar(require("./MethodNotImplementedError"), exports);
|
|
@@ -28,10 +28,10 @@ function sayHandler(ari, voiceClient) {
|
|
|
28
28
|
const { sessionRef: channelId } = request;
|
|
29
29
|
sayRequestSchema.parse(request);
|
|
30
30
|
const playbackRef = request.playbackRef || (0, nanoid_1.nanoid)(10);
|
|
31
|
-
const
|
|
31
|
+
const mediaId = yield voiceClient.synthesize(request.text, request.options ? pb_util_1.struct.decode(request.options) : {});
|
|
32
32
|
yield ari.channels.play({
|
|
33
33
|
channelId,
|
|
34
|
-
media: getMediaUrl(
|
|
34
|
+
media: getMediaUrl(mediaId),
|
|
35
35
|
playbackId: playbackRef
|
|
36
36
|
});
|
|
37
37
|
yield (0, awaitForPlaybackFinished_1.awaitForPlaybackFinished)(ari, playbackRef);
|
|
@@ -35,8 +35,8 @@ const handleStasisEnd_1 = require("./handleStasisEnd");
|
|
|
35
35
|
const handleStasisStart_1 = require("./handleStasisStart");
|
|
36
36
|
const envs_1 = require("../../../envs");
|
|
37
37
|
const utils_1 = require("../../../utils");
|
|
38
|
-
const makeGetChannelVar_1 = require("../../makeGetChannelVar");
|
|
39
38
|
const types_1 = require("../../types");
|
|
39
|
+
const makeGetChannelVar_1 = require("../../utils/makeGetChannelVar");
|
|
40
40
|
// TODO: Needs request validation
|
|
41
41
|
function dialHandler(ari, voiceClient) {
|
|
42
42
|
return (request) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getTtsConfig = getTtsConfig;
|
|
4
4
|
const findIntegrationsCredentials_1 = require("./findIntegrationsCredentials");
|
|
5
|
-
const envs_1 = require("../../envs");
|
|
6
5
|
function getTtsConfig(integrations, app) {
|
|
7
6
|
const config = app.textToSpeech.config;
|
|
8
7
|
const credentials = (0, findIntegrationsCredentials_1.findIntegrationsCredentials)(integrations, app.textToSpeech.productRef);
|
|
9
8
|
return {
|
|
10
9
|
config,
|
|
11
|
-
credentials
|
|
12
|
-
pathToFiles: envs_1.TTS_PATH_TO_FILES
|
|
10
|
+
credentials
|
|
13
11
|
};
|
|
14
12
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Channel, Client, StasisStart } from "ari-client";
|
|
2
2
|
import { CreateContainer } from "./integrations/types";
|
|
3
3
|
import { VoiceClient } from "./types";
|
|
4
|
-
declare function makeCreateVoiceClient(createContainer: CreateContainer): (params: {
|
|
4
|
+
declare function makeCreateVoiceClient(createContainer: CreateContainer, filesServer: any): (params: {
|
|
5
5
|
ari: Client;
|
|
6
6
|
event: StasisStart;
|
|
7
7
|
channel: Channel;
|
|
@@ -30,21 +30,21 @@ exports.makeCreateVoiceClient = makeCreateVoiceClient;
|
|
|
30
30
|
*/
|
|
31
31
|
const identity_1 = require("@fonoster/identity");
|
|
32
32
|
const logger_1 = require("@fonoster/logger");
|
|
33
|
-
const makeGetChannelVar_1 = require("./makeGetChannelVar");
|
|
34
33
|
const types_1 = require("./types");
|
|
34
|
+
const makeGetChannelVarWithoutThrow_1 = require("./utils/makeGetChannelVarWithoutThrow");
|
|
35
35
|
const VoiceClientImpl_1 = require("./VoiceClientImpl");
|
|
36
36
|
const identityConfig_1 = require("../core/identityConfig");
|
|
37
37
|
const mapCallDirectionToEnum_1 = require("../events/mapCallDirectionToEnum");
|
|
38
38
|
const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
|
|
39
39
|
const createToken = (0, identity_1.createCallAccessToken)(identityConfig_1.identityConfig);
|
|
40
40
|
// Note: By the time the call arrives here the owner of the app MUST be authenticated
|
|
41
|
-
function makeCreateVoiceClient(createContainer) {
|
|
41
|
+
function makeCreateVoiceClient(createContainer, filesServer) {
|
|
42
42
|
return (params) => __awaiter(this, void 0, void 0, function* () {
|
|
43
43
|
var _a, _b, _c, _d;
|
|
44
44
|
const { ari, event, channel } = params;
|
|
45
45
|
const { id: sessionRef, caller } = event.channel;
|
|
46
46
|
const { name: callerName, number: callerNumber } = caller;
|
|
47
|
-
const getChannelVar = (0,
|
|
47
|
+
const getChannelVar = (0, makeGetChannelVarWithoutThrow_1.makeGetChannelVarWithoutThrow)(channel);
|
|
48
48
|
// Variables set by Asterisk's dialplan
|
|
49
49
|
const callDirection = (_a = (yield getChannelVar(types_1.ChannelVar.CALL_DIRECTION))) === null || _a === void 0 ? void 0 : _a.value;
|
|
50
50
|
const appRef = (_b = (yield getChannelVar(types_1.ChannelVar.APP_REF))) === null || _b === void 0 ? void 0 : _b.value;
|
|
@@ -69,6 +69,6 @@ function makeCreateVoiceClient(createContainer) {
|
|
|
69
69
|
callerNumber,
|
|
70
70
|
ingressNumber
|
|
71
71
|
});
|
|
72
|
-
return new VoiceClientImpl_1.VoiceClientImpl({ ari, config, tts, stt });
|
|
72
|
+
return new VoiceClientImpl_1.VoiceClientImpl({ ari, config, tts, stt }, filesServer);
|
|
73
73
|
});
|
|
74
74
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AbstractSpeechToText = void 0;
|
|
4
|
-
const MethodNotImplementedError_1 = require("../MethodNotImplementedError");
|
|
4
|
+
const MethodNotImplementedError_1 = require("../errors/MethodNotImplementedError");
|
|
5
5
|
class AbstractSpeechToText {
|
|
6
6
|
constructor(config) {
|
|
7
7
|
this.config = config;
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
+
import { Readable } from "stream";
|
|
1
2
|
import * as z from "zod";
|
|
2
|
-
import { SynthOptions
|
|
3
|
-
declare abstract class AbstractTextToSpeech<E,
|
|
3
|
+
import { SynthOptions } from "./types";
|
|
4
|
+
declare abstract class AbstractTextToSpeech<E, S extends SynthOptions = SynthOptions> {
|
|
4
5
|
abstract readonly engineName: E;
|
|
5
6
|
protected abstract OUTPUT_FORMAT: "wav" | "sln16";
|
|
6
7
|
protected abstract CACHING_FIELDS: string[];
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
protected getFilenameWithoutExtension(filename: string): string;
|
|
12
|
-
protected getFullPathToFile(filename: string): string;
|
|
13
|
-
abstract synthesize(text: string, options: S): Promise<string>;
|
|
8
|
+
abstract synthesize(text: string, options: S): Promise<{
|
|
9
|
+
ref: string;
|
|
10
|
+
stream: Readable;
|
|
11
|
+
}>;
|
|
14
12
|
static getConfigValidationSchema(): z.Schema;
|
|
15
13
|
static getCredentialsValidationSchema(): z.Schema;
|
|
14
|
+
protected createMediaReference(): string;
|
|
16
15
|
getName(): E;
|
|
17
16
|
}
|
|
18
17
|
export { AbstractTextToSpeech };
|
|
@@ -1,80 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
3
|
exports.AbstractTextToSpeech = void 0;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* http://github.com/fonoster/fonoster
|
|
30
|
-
*
|
|
31
|
-
* This file is part of Fonoster
|
|
32
|
-
*
|
|
33
|
-
* Licensed under the MIT License (the "License");
|
|
34
|
-
* you may not use this file except in compliance with
|
|
35
|
-
* the License. You may obtain a copy of the License at
|
|
36
|
-
*
|
|
37
|
-
* https://opensource.org/licenses/MIT
|
|
38
|
-
*
|
|
39
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
40
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
41
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
42
|
-
* See the License for the specific language governing permissions and
|
|
43
|
-
* limitations under the License.
|
|
44
|
-
*/
|
|
45
|
-
const fs = __importStar(require("fs"));
|
|
46
|
-
const logger_1 = require("@fonoster/logger");
|
|
47
|
-
const computeFilename_1 = require("./computeFilename");
|
|
48
|
-
const MethodNotImplementedError_1 = require("../MethodNotImplementedError");
|
|
49
|
-
const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const MethodNotImplementedError_1 = require("../errors/MethodNotImplementedError");
|
|
50
6
|
class AbstractTextToSpeech {
|
|
51
|
-
constructor(config) {
|
|
52
|
-
logger.silly("tts pathToFiles", { engine: config.pathToFiles });
|
|
53
|
-
this.config = config;
|
|
54
|
-
}
|
|
55
|
-
createFilename(text, options) {
|
|
56
|
-
return (0, computeFilename_1.computeFilename)({
|
|
57
|
-
text,
|
|
58
|
-
options,
|
|
59
|
-
cachingFields: this.CACHING_FIELDS,
|
|
60
|
-
format: this.OUTPUT_FORMAT
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
fileExists(filename) {
|
|
64
|
-
return fs.existsSync(filename);
|
|
65
|
-
}
|
|
66
|
-
getFilenameWithoutExtension(filename) {
|
|
67
|
-
return filename.replace(`.${this.OUTPUT_FORMAT}`, "");
|
|
68
|
-
}
|
|
69
|
-
getFullPathToFile(filename) {
|
|
70
|
-
return `${this.config.pathToFiles}/${filename}`;
|
|
71
|
-
}
|
|
72
7
|
static getConfigValidationSchema() {
|
|
73
8
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
74
9
|
}
|
|
75
10
|
static getCredentialsValidationSchema() {
|
|
76
11
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
77
12
|
}
|
|
13
|
+
createMediaReference() {
|
|
14
|
+
return (0, uuid_1.v4)();
|
|
15
|
+
}
|
|
78
16
|
getName() {
|
|
79
17
|
return this.engineName;
|
|
80
18
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { Readable } from "stream";
|
|
1
2
|
import * as sdk from "microsoft-cognitiveservices-speech-sdk";
|
|
2
3
|
import * as z from "zod";
|
|
3
4
|
import { AbstractTextToSpeech } from "./AbstractTextToSpeech";
|
|
4
|
-
import { SynthOptions
|
|
5
|
+
import { SynthOptions } from "./types";
|
|
5
6
|
declare const ENGINE_NAME = "tts.azure";
|
|
6
|
-
type AzureTTSConfig =
|
|
7
|
+
type AzureTTSConfig = {
|
|
7
8
|
[key: string]: Record<string, string>;
|
|
8
9
|
credentials: {
|
|
9
10
|
subscriptionKey: string;
|
|
@@ -12,15 +13,16 @@ type AzureTTSConfig = TtsConfig & {
|
|
|
12
13
|
};
|
|
13
14
|
declare class Azure extends AbstractTextToSpeech<typeof ENGINE_NAME> {
|
|
14
15
|
config: AzureTTSConfig;
|
|
15
|
-
pathToFiles: string;
|
|
16
16
|
readonly engineName = "tts.azure";
|
|
17
17
|
protected readonly OUTPUT_FORMAT = "sln16";
|
|
18
18
|
protected readonly CACHING_FIELDS: string[];
|
|
19
19
|
constructor(config: AzureTTSConfig);
|
|
20
|
-
synthesize(text: string, options: SynthOptions): Promise<
|
|
20
|
+
synthesize(text: string, options: SynthOptions): Promise<{
|
|
21
|
+
ref: string;
|
|
22
|
+
stream: Readable;
|
|
23
|
+
}>;
|
|
21
24
|
doSynthesize(params: {
|
|
22
25
|
text: string;
|
|
23
|
-
filename: string;
|
|
24
26
|
speechConfig: sdk.SpeechConfig;
|
|
25
27
|
isSSML?: boolean;
|
|
26
28
|
}): Promise<unknown>;
|
package/dist/voice/tts/Azure.js
CHANGED
|
@@ -51,6 +51,7 @@ exports.ENGINE_NAME = exports.Azure = void 0;
|
|
|
51
51
|
* See the License for the specific language governing permissions and
|
|
52
52
|
* limitations under the License.
|
|
53
53
|
*/
|
|
54
|
+
const stream_1 = require("stream");
|
|
54
55
|
const common_1 = require("@fonoster/common");
|
|
55
56
|
const logger_1 = require("@fonoster/logger");
|
|
56
57
|
const sdk = __importStar(require("microsoft-cognitiveservices-speech-sdk"));
|
|
@@ -60,9 +61,10 @@ const isSsml_1 = require("./isSsml");
|
|
|
60
61
|
const ENGINE_NAME = "tts.azure";
|
|
61
62
|
exports.ENGINE_NAME = ENGINE_NAME;
|
|
62
63
|
const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
|
|
64
|
+
// XXX: Must re-implement to provide an id an a Readable stream
|
|
63
65
|
class Azure extends AbstractTextToSpeech_1.AbstractTextToSpeech {
|
|
64
66
|
constructor(config) {
|
|
65
|
-
super(
|
|
67
|
+
super();
|
|
66
68
|
this.engineName = ENGINE_NAME;
|
|
67
69
|
this.OUTPUT_FORMAT = "sln16";
|
|
68
70
|
this.CACHING_FIELDS = ["voice"];
|
|
@@ -71,11 +73,6 @@ class Azure extends AbstractTextToSpeech_1.AbstractTextToSpeech {
|
|
|
71
73
|
synthesize(text, options) {
|
|
72
74
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
75
|
logger.verbose(`synthesize [input: ${text}, isSsml=${(0, isSsml_1.isSsml)(text)} options: ${JSON.stringify(options)}]`);
|
|
74
|
-
const effectiveOptions = Object.assign(Object.assign({}, this.config), options);
|
|
75
|
-
const filename = this.createFilename(text, effectiveOptions);
|
|
76
|
-
if (this.fileExists(this.getFullPathToFile(filename))) {
|
|
77
|
-
return this.getFilenameWithoutExtension(filename);
|
|
78
|
-
}
|
|
79
76
|
const { subscriptionKey, serviceRegion } = this.config.credentials;
|
|
80
77
|
const speechConfig = sdk.SpeechConfig.fromSubscription(subscriptionKey, serviceRegion);
|
|
81
78
|
speechConfig.speechSynthesisVoiceName = options.voice;
|
|
@@ -83,24 +80,24 @@ class Azure extends AbstractTextToSpeech_1.AbstractTextToSpeech {
|
|
|
83
80
|
sdk.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm;
|
|
84
81
|
yield this.doSynthesize({
|
|
85
82
|
text,
|
|
86
|
-
filename: this.getFullPathToFile(filename),
|
|
87
83
|
speechConfig,
|
|
88
84
|
isSSML: (0, isSsml_1.isSsml)(text)
|
|
89
85
|
});
|
|
90
|
-
|
|
86
|
+
const ref = this.createMediaReference();
|
|
87
|
+
// TODO: Fix this placeholder
|
|
88
|
+
return { ref, stream: new stream_1.Readable() };
|
|
91
89
|
});
|
|
92
90
|
}
|
|
93
91
|
doSynthesize(params) {
|
|
94
92
|
return __awaiter(this, void 0, void 0, function* () {
|
|
95
|
-
const { text,
|
|
96
|
-
const
|
|
97
|
-
const synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig);
|
|
93
|
+
const { text, speechConfig } = params;
|
|
94
|
+
const synthesizer = new sdk.SpeechSynthesizer(speechConfig);
|
|
98
95
|
// FIXME: Let's turn this into constants
|
|
99
96
|
const func = params.isSSML ? "speakSsmlAsync" : "speakTextAsync";
|
|
100
97
|
return new Promise((resolve, reject) => {
|
|
101
98
|
synthesizer[func](text, function (result) {
|
|
102
99
|
if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
|
|
103
|
-
resolve(
|
|
100
|
+
resolve("");
|
|
104
101
|
}
|
|
105
102
|
else {
|
|
106
103
|
reject(new Error("speech synthesis canceled: " + result.errorDetails));
|