n8n-nodes-salutespeech
Version:
User-friendly SaluteSpeech (Sber) community node
385 lines • 16.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SaluteSpeech = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const constants_1 = require("./constants");
const SaluteSpeechApiClient_1 = require("../shared/SaluteSpeechApiClient");
const node_https_1 = require("node:https");
class SaluteSpeech {
constructor() {
this.description = {
displayName: 'SaluteSpeech',
name: 'saluteSpeech',
icon: 'file:temp-salute.svg',
group: ['input'],
version: 1,
defaults: {
name: 'SaluteSpeech API',
},
description: 'Transform your voice into text',
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'saluteSpeechApi',
required: true,
},
],
requestDefaults: {
skipSslCertificateValidation: true,
},
properties: [
{
displayName: 'Ресурс',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Речь',
value: 'speech',
description: 'Работа с готовой речью',
},
{ name: 'Текст', value: 'text', description: 'Работа с текстом' },
],
default: 'speech',
},
{
displayName: 'Операция',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['speech'],
},
},
options: [
{
name: 'Распознать речь',
value: 'recognize',
description: 'Распознать речь в аудио файле',
action: 'Speech recognition',
},
],
default: 'recognize',
},
{
displayName: 'Операция',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['text'],
},
},
options: [
{
name: 'Синтезировать речь',
value: 'synthesize',
action: 'Speech synthesis',
},
],
default: 'synthesize',
},
{
displayName: 'Voice Binary Field',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
required: true,
hint: 'Имя поля с аудио, которое надо отправить.',
displayOptions: {
show: {
operation: ['recognize'],
resource: ['speech'],
},
},
placeholder: '',
description: 'Name of the binary property that contains the data to upload',
},
{
displayName: 'Language',
name: 'language',
type: 'options',
description: 'Source language',
options: constants_1.smartSpeechLanguages,
default: 'ru-RU',
displayOptions: {
show: {
operation: ['recognize'],
resource: ['speech'],
},
},
required: true,
},
{
displayName: 'Модель',
name: 'model',
type: 'options',
description: 'Модель для анализа аудио',
options: constants_1.smartSpeechModels,
default: 'general',
displayOptions: {
show: {
operation: ['recognize'],
resource: ['speech'],
},
},
required: true,
},
{
displayName: 'Фильтр цензуры',
name: 'profanityFilter',
type: 'boolean',
default: false,
displayOptions: {
show: {
operation: ['recognize'],
resource: ['speech'],
},
},
description: 'Включить/выключить фильтр обсценной лексики',
},
{
displayName: 'Частота дискретизации',
name: 'sampleRate',
type: 'number',
default: 16000,
displayOptions: {
show: {
operation: ['recognize'],
resource: ['speech'],
},
},
description: 'Частота дискретизации. Максимально возможное значение — до 96000Гц. Меняйте, только если знаете, что делаете.',
},
{
displayName: 'Кол-во каналов',
name: 'channelsCount',
type: 'number',
default: 1,
displayOptions: {
show: {
operation: ['recognize'],
resource: ['speech'],
},
},
description: 'Количество каналов в многоканальном аудио. Для OGG войсов - 1 канал. Меняйте, только если знаете, что делаете.',
},
{
displayName: 'Соединить ответ',
name: 'combineArray',
type: 'boolean',
default: false,
displayOptions: {
show: {
operation: ['recognize'],
resource: ['speech'],
},
},
description: 'Соединить ответ в одну строку?',
},
{
displayName: 'Символ соединения',
name: 'combineSymbol',
type: 'string',
default: '\n',
displayOptions: {
show: {
operation: ['recognize'],
resource: ['speech'],
combineArray: [true],
},
},
typeOptions: {
rows: 2,
},
description: 'Символ соединения строк. По умолчанию - перенос строки.',
hint: 'Переключите на Expression, чтобы добавить переносы строк',
},
{
displayName: 'Voice',
name: 'voiceId',
type: 'options',
description: 'Voice to use',
options: constants_1.smartSpeechVoices,
default: 'Nec',
displayOptions: {
show: {
operation: ['synthesize'],
},
},
required: true,
},
{
displayName: 'Format',
name: 'format',
type: 'options',
description: 'Output audio format (codec)',
options: constants_1.smartSpeechFormats,
default: 'wav16',
displayOptions: {
show: {
operation: ['synthesize'],
},
},
required: true,
},
{
displayName: 'Frequency',
name: 'freq',
type: 'options',
description: 'Frequency of audio (Hz)',
options: constants_1.smartSpeechHertz,
default: '24000',
displayOptions: {
show: {
operation: ['synthesize'],
},
},
required: true,
},
{
displayName: 'Текст',
name: 'textInput',
type: 'string',
description: 'Текст, который нужно озвучить',
default: '',
placeholder: 'Внимание, говорит Москва...',
displayOptions: {
show: {
operation: ['synthesize'],
},
},
typeOptions: {
rows: 4,
},
required: true,
},
{
displayName: 'Output Binary Property Name',
name: 'outputPropertyName',
type: 'string',
description: 'Название переменной, в которой выложить аудио файл',
default: 'data',
displayOptions: {
show: {
operation: ['synthesize'],
},
},
required: true,
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
const credentials = await this.getCredentials('saluteSpeechApi');
const scope = credentials.scope ? String(credentials.scope) : 'SALUTE_SPEECH_PERS';
await SaluteSpeechApiClient_1.SaluteSpeechApiClient.updateConfig({
authKey: credentials.authorizationKey,
scope,
baseUrl: String(credentials.base_url) || 'https://smartspeech.sber.ru/rest/v1',
authUrl: credentials.auth_url
? `${credentials.auth_url}/api/v2/oauth`
: 'https://ngw.devices.sberbank.ru:9443/api/v2/oauth',
httpsAgent: new node_https_1.Agent({
rejectUnauthorized: false,
}),
});
for (let i = 0; i < items.length; i++) {
try {
if (resource === 'speech' && operation === 'recognize') {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0);
const model = this.getNodeParameter('model', 0);
const profanity = this.getNodeParameter('profanityFilter', 0);
const lang = this.getNodeParameter('language', 0);
const channelsCount = this.getNodeParameter('channelsCount', 0);
const combineArray = this.getNodeParameter('combineArray', 0);
const itemBinaryData = items[i].binary[binaryPropertyName];
const itemBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
let mimeType = itemBinaryData.mimeType;
if (mimeType.includes('ogg')) {
mimeType = 'audio/ogg;codecs=opus';
}
const speechRecognized = await SaluteSpeechApiClient_1.SaluteSpeechApiClient.speechRecognize(new File([itemBuffer], itemBinaryData.fileName || `file${itemBinaryData.fileExtension || 'bin'}`, {
type: mimeType,
}), lang, profanity, model, channelsCount);
let final = speechRecognized;
if (combineArray) {
const combineSymbol = this.getNodeParameter('combineSymbol', 0);
final = final.join(combineSymbol);
}
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray({ response: final }), {
itemData: { item: i },
});
if (Array.isArray(executionData)) {
returnData.push(...executionData);
}
else {
returnData.push(executionData);
}
}
if (resource === 'text' && operation === 'synthesize') {
const voiceId = this.getNodeParameter('voiceId', 0);
const format = this.getNodeParameter('format', 0);
const freq = this.getNodeParameter('freq', 0);
const text = this.getNodeParameter('textInput', 0);
const outputPropertyName = this.getNodeParameter('outputPropertyName', 0);
const synthesis = await SaluteSpeechApiClient_1.SaluteSpeechApiClient.textSynthesize(text, voiceId, format, freq);
const bin = await this.helpers.prepareBinaryData(synthesis.buffer, synthesis.fileName, synthesis.mime);
returnData.push({
json: { format: bin.format },
binary: { [outputPropertyName]: bin },
});
}
}
catch (error) {
let errorMessage;
if (error instanceof Error) {
errorMessage = error.message;
}
else if (typeof error === 'object' && error !== null) {
if ('response' in error &&
typeof error.response === 'object' &&
error.response !== null) {
const response = error.response;
if (response.data && typeof response.data === 'object') {
errorMessage = JSON.stringify(response.data);
}
else if (response.statusText) {
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
}
else {
errorMessage = `HTTP Error: ${response.status}`;
}
}
else {
errorMessage = JSON.stringify(error);
}
}
else {
errorMessage = String(error);
}
if (this.continueOnFail()) {
returnData.push({
json: {
error: errorMessage,
},
pairedItem: { item: i },
});
continue;
}
throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage, { itemIndex: i });
}
}
return [returnData];
}
}
exports.SaluteSpeech = SaluteSpeech;
//# sourceMappingURL=SaluteSpeech.node.js.map