diff --git a/packages/nodes-base/nodes/Aws/Transcribe/AwsTranscribe.node.json b/packages/nodes-base/nodes/Aws/Transcribe/AwsTranscribe.node.json
new file mode 100644
index 0000000000..314e6f04da
--- /dev/null
+++ b/packages/nodes-base/nodes/Aws/Transcribe/AwsTranscribe.node.json
@@ -0,0 +1,20 @@
+{
+ "node": "n8n-nodes-base.awsTranscribe",
+ "nodeVersion": "1.0",
+ "codexVersion": "1.0",
+ "categories": [
+ "Development"
+ ],
+ "resources": {
+ "credentialDocumentation": [
+ {
+ "url": "https://docs.n8n.io/credentials/aws"
+ }
+ ],
+ "primaryDocumentation": [
+ {
+ "url": "https://docs.n8n.io/nodes/n8n-nodes-base.awsTranscribe/"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/nodes-base/nodes/Aws/Transcribe/AwsTranscribe.node.ts b/packages/nodes-base/nodes/Aws/Transcribe/AwsTranscribe.node.ts
new file mode 100644
index 0000000000..aa81b2d4ff
--- /dev/null
+++ b/packages/nodes-base/nodes/Aws/Transcribe/AwsTranscribe.node.ts
@@ -0,0 +1,544 @@
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import {
+ awsApiRequestREST,
+ awsApiRequestRESTAllItems,
+} from './GenericFunctions';
+
+export class AwsTranscribe implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'AWS Transcribe',
+ name: 'awsTranscribe',
+ icon: 'file:transcribe.svg',
+ group: ['output'],
+ version: 1,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Sends data to AWS Transcribe',
+ defaults: {
+ name: 'AWS Transcribe',
+ color: '#5aa08d',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'aws',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ options: [
+ {
+ name: 'Transcription Job',
+ value: 'transcriptionJob',
+ },
+ ],
+ default: 'transcriptionJob',
+ description: 'Resource to operate on.',
+ },
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a transcription job',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a transcription job',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a transcription job',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all transcription jobs',
+ },
+ ],
+ default: 'create',
+ description: 'Operation to perform.',
+ },
+ {
+ displayName: 'Job Name',
+ name: 'transcriptionJobName',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'create',
+ 'get',
+ 'delete',
+ ],
+ },
+ },
+ description: 'The name of the job.',
+ },
+ {
+ displayName: 'Media File URI',
+ name: 'mediaFileUri',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'The S3 object location of the input media file. ',
+ },
+ {
+ displayName: 'Detect Language',
+ name: 'detectLanguage',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ default: false,
+ description: 'Set this field to true to enable automatic language identification.',
+ },
+ {
+ displayName: 'Language',
+ name: 'languageCode',
+ type: 'options',
+ options: [
+ {
+ name: 'American English',
+ value: 'en-US',
+ },
+ {
+ name: 'British English',
+ value: 'en-GB',
+ },
+ {
+ name: 'Irish English',
+ value: 'en-IE',
+ },
+ {
+ name: 'Indian English',
+ value: 'en-IN',
+ },
+ {
+ name: 'Spanish',
+ value: 'es-ES',
+ },
+ {
+ name: 'German',
+ value: 'de-DE',
+ },
+ {
+ name: 'Russian',
+ value: 'ru-RU',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'create',
+ ],
+ detectLanguage: [
+ false,
+ ],
+ },
+ },
+ default: 'en-US',
+ description: 'Language used in the input media file.',
+ },
+ // ----------------------------------
+ // Transcription Job Settings
+ // ----------------------------------
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ displayOptions: {
+ show: {
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Channel Identification',
+ name: 'channelIdentification',
+ type: 'boolean',
+ default: false,
+ description: `Instructs Amazon Transcribe to process each audiochannel separately
+ and then merge the transcription output of each channel into a single transcription.
+ You can't set both Max Speaker Labels and Channel Identification in the same request.
+ If you set both, your request returns a BadRequestException.`,
+ },
+ {
+ displayName: 'Max Alternatives',
+ name: 'maxAlternatives',
+ type: 'number',
+ default: 2,
+ typeOptions: {
+ minValue: 2,
+ maxValue: 10,
+ },
+ description: 'The number of alternative transcriptions that the service should return.',
+ },
+ {
+ displayName: 'Max Speaker Labels',
+ name: 'maxSpeakerLabels',
+ type: 'number',
+ default: 2,
+ typeOptions: {
+ minValue: 2,
+ maxValue: 10,
+ },
+ description: `The maximum number of speakers to identify in the input audio.
+ If there are more speakers in the audio than this number, multiple speakers are
+ identified as a single speaker.`,
+ },
+ {
+ displayName: 'Vocabulary Name',
+ name: 'vocabularyName',
+ type: 'string',
+ default: '',
+ description: 'Name of vocabulary to use when processing the transcription job.',
+ },
+ {
+ displayName: 'Vocabulary Filter Name',
+ name: 'vocabularyFilterName',
+ type: 'string',
+ default: '',
+ description: `The name of the vocabulary filter to use when transcribing the audio.
+ The filter that you specify must have the same language code as the transcription job.`,
+ },
+ {
+ displayName: 'Vocabulary Filter Method',
+ name: 'vocabularyFilterMethod',
+ type: 'options',
+ options: [
+ {
+ name: 'Remove',
+ value: 'remove',
+ },
+ {
+ name: 'Mask',
+ value: 'mask',
+ },
+ {
+ name: 'Tag',
+ value: 'tag',
+ },
+
+ ],
+ default: 'remove',
+ description: `Set to mask to remove filtered text from the transcript and replace it with three asterisks ("***") as placeholder text.
+ Set to remove to remove filtered text from the transcript without using placeholder text. Set to tag to mark the word in the transcription
+ output that matches the vocabulary filter. When you set the filter method to tag, the words matching your vocabulary filter are not masked or removed.`,
+ },
+ ],
+ },
+ {
+ displayName: 'Return Transcript',
+ name: 'returnTranscript',
+ type: 'boolean',
+ default: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'get',
+ ],
+ },
+ },
+ description: 'By default, the response only contains metadata about the transcript.
Enable this option to retrieve the transcript instead.',
+ },
+ {
+ displayName: 'Simple',
+ name: 'simple',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'get',
+ ],
+ returnTranscript: [
+ true,
+ ],
+ },
+ },
+ default: true,
+ description: 'Return a simplified version of the response instead of the raw data.',
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 20,
+ typeOptions: {
+ minValue: 1,
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ description: 'The maximum number of results to return',
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Filter',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'transcriptionJob',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Job Name Contains',
+ name: 'jobNameContains',
+ type: 'string',
+ description: 'Return only transcription jobs whose name contains the specified string.',
+ default: '',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ options: [
+ {
+ name: 'Completed',
+ value: 'COMPLETED',
+ },
+ {
+ name: 'Failed',
+ value: 'FAILED',
+ },
+ {
+ name: 'In Progress',
+ value: 'IN_PROGRESS',
+ },
+ {
+ name: 'Queued',
+ value: 'QUEUED',
+ },
+ ],
+ description: 'Return only transcription jobs with the specified status.',
+ default: 'COMPLETED',
+ },
+ ],
+ },
+ ],
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: IDataObject[] = [];
+ let responseData;
+ const resource = this.getNodeParameter('resource', 0) as string;
+ const operation = this.getNodeParameter('operation', 0) as string;
+ for (let i = 0; i < items.length; i++) {
+ if (resource === 'transcriptionJob') {
+ //https://docs.aws.amazon.com/comprehend/latest/dg/API_DetectDominantLanguage.html
+ if (operation === 'create') {
+ const transcriptionJobName = this.getNodeParameter('transcriptionJobName', i) as string;
+ const mediaFileUri = this.getNodeParameter('mediaFileUri', i) as string;
+ const detectLang = this.getNodeParameter('detectLanguage', i) as boolean;
+
+ const options = this.getNodeParameter('options', i, {}) as IDataObject;
+
+ const body: IDataObject = {
+ TranscriptionJobName: transcriptionJobName,
+ Media: {
+ MediaFileUri: mediaFileUri,
+ },
+ };
+
+ if (detectLang) {
+ body.IdentifyLanguage = detectLang;
+ } else {
+ body.LanguageCode = this.getNodeParameter('languageCode', i) as string;
+ }
+
+ if (options.channelIdentification) {
+ Object.assign(body.Settings, { ChannelIdentification: options.channelIdentification });
+ }
+
+ if (options.MaxAlternatives) {
+ Object.assign(body.Settings, {
+ ShowAlternatives: options.maxAlternatives,
+ MaxAlternatives: options.maxAlternatives,
+ });
+ }
+
+ if (options.showSpeakerLabels) {
+ Object.assign(body.Settings, {
+ ShowSpeakerLabels: options.showSpeakerLabels,
+ MaxSpeakerLabels: options.maxSpeakerLabels,
+ });
+ }
+
+ if (options.vocabularyName) {
+ Object.assign(body.Settings, {
+ VocabularyName: options.vocabularyName,
+ });
+ }
+
+ if (options.vocabularyFilterName) {
+ Object.assign(body.Settings, {
+ VocabularyFilterName: options.vocabularyFilterName,
+ });
+ }
+
+ if (options.vocabularyFilterMethod) {
+ Object.assign(body.Settings, {
+ VocabularyFilterMethod: options.vocabularyFilterMethod,
+ });
+ }
+
+ const action = 'Transcribe.StartTranscriptionJob';
+ responseData = await awsApiRequestREST.call(this, 'transcribe', 'POST', '', JSON.stringify(body), { 'x-amz-target': action, 'Content-Type': 'application/x-amz-json-1.1' });
+ responseData = responseData.TranscriptionJob;
+ }
+ //https://docs.aws.amazon.com/transcribe/latest/dg/API_DeleteTranscriptionJob.html
+ if (operation === 'delete') {
+ const transcriptionJobName = this.getNodeParameter('transcriptionJobName', i) as string;
+
+ const body: IDataObject = {
+ TranscriptionJobName: transcriptionJobName,
+ };
+
+ const action = 'Transcribe.DeleteTranscriptionJob';
+ responseData = await awsApiRequestREST.call(this, 'transcribe', 'POST', '', JSON.stringify(body), { 'x-amz-target': action, 'Content-Type': 'application/x-amz-json-1.1' });
+ responseData = { success: true };
+ }
+ //https://docs.aws.amazon.com/transcribe/latest/dg/API_GetTranscriptionJob.html
+ if (operation === 'get') {
+ const transcriptionJobName = this.getNodeParameter('transcriptionJobName', i) as string;
+ const resolve = this.getNodeParameter('returnTranscript', 0) as boolean;
+
+ const body: IDataObject = {
+ TranscriptionJobName: transcriptionJobName,
+ };
+
+ const action = 'Transcribe.GetTranscriptionJob';
+ responseData = await awsApiRequestREST.call(this, 'transcribe', 'POST', '', JSON.stringify(body), { 'x-amz-target': action, 'Content-Type': 'application/x-amz-json-1.1' });
+ responseData = responseData.TranscriptionJob;
+
+ if (resolve === true && responseData.TranscriptionJobStatus === 'COMPLETED') {
+ responseData = await this.helpers.request({ method: 'GET', uri: responseData.Transcript.TranscriptFileUri, json: true });
+ const simple = this.getNodeParameter('simple', 0) as boolean;
+ if (simple === true) {
+ responseData = { transcript: responseData.results.transcripts.map((data: IDataObject) => data.transcript).join(' ') };
+ }
+ }
+ }
+ //https://docs.aws.amazon.com/transcribe/latest/dg/API_ListTranscriptionJobs.html
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ const filters = this.getNodeParameter('filters', i) as IDataObject;
+ const action = 'Transcribe.ListTranscriptionJobs';
+ const body: IDataObject = {};
+
+ if (filters.status) {
+ body['Status'] = filters.status;
+ }
+
+ if (filters.jobNameContains) {
+ body['JobNameContains'] = filters.jobNameContains;
+ }
+
+ if (returnAll === true) {
+ responseData = await awsApiRequestRESTAllItems.call(this, 'TranscriptionJobSummaries', 'transcribe', 'POST', '', JSON.stringify(body), { 'x-amz-target': action, 'Content-Type': 'application/x-amz-json-1.1' });
+
+ } else {
+ const limit = this.getNodeParameter('limit', i) as number;
+ body['MaxResults'] = limit;
+ responseData = await awsApiRequestREST.call(this, 'transcribe', 'POST', '', JSON.stringify(body), { 'x-amz-target': action, 'Content-Type': 'application/x-amz-json-1.1' });
+ responseData = responseData.TranscriptionJobSummaries;
+ }
+ }
+ }
+
+ if (Array.isArray(responseData)) {
+ returnData.push.apply(returnData, responseData as IDataObject[]);
+ } else {
+ returnData.push(responseData as IDataObject);
+ }
+ }
+ return [this.helpers.returnJsonArray(returnData)];
+ }
+}
diff --git a/packages/nodes-base/nodes/Aws/Transcribe/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Transcribe/GenericFunctions.ts
new file mode 100644
index 0000000000..177b349906
--- /dev/null
+++ b/packages/nodes-base/nodes/Aws/Transcribe/GenericFunctions.ts
@@ -0,0 +1,107 @@
+import {
+ URL,
+} from 'url';
+
+import {
+ sign,
+} from 'aws4';
+
+import {
+ OptionsWithUri,
+} from 'request';
+
+import {
+ IExecuteFunctions,
+ IHookFunctions,
+ ILoadOptionsFunctions,
+ IWebhookFunctions,
+} from 'n8n-core';
+
+import {
+ ICredentialDataDecryptedObject,
+ IDataObject,
+ NodeApiError,
+ NodeOperationError,
+} from 'n8n-workflow';
+
+import {
+ get,
+} from 'lodash';
+
+function getEndpointForService(service: string, credentials: ICredentialDataDecryptedObject): string {
+ let endpoint;
+ if (service === 'lambda' && credentials.lambdaEndpoint) {
+ endpoint = credentials.lambdaEndpoint;
+ } else if (service === 'sns' && credentials.snsEndpoint) {
+ endpoint = credentials.snsEndpoint;
+ } else {
+ endpoint = `https://${service}.${credentials.region}.amazonaws.com`;
+ }
+ return (endpoint as string).replace('{region}', credentials.region as string);
+}
+
+export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise { // tslint:disable-line:no-any
+ const credentials = this.getCredentials('aws');
+ if (credentials === undefined) {
+ throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
+ }
+
+ // Concatenate path and instantiate URL object so it parses correctly query strings
+ const endpoint = new URL(getEndpointForService(service, credentials) + path);
+
+ // Sign AWS API request with the user credentials
+ const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body };
+ sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
+
+
+ const options: OptionsWithUri = {
+ headers: signOpts.headers,
+ method,
+ uri: endpoint.href,
+ body: signOpts.body,
+ };
+
+ try {
+ return await this.helpers.request!(options);
+ } catch (error) {
+ throw new NodeApiError(this.getNode(), error); // no XML parsing needed
+ }
+}
+
+export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise { // tslint:disable-line:no-any
+ const response = await awsApiRequest.call(this, service, method, path, body, headers);
+ try {
+ return JSON.parse(response);
+ } catch (error) {
+ return response;
+ }
+}
+
+export async function awsApiRequestRESTAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, service: string, method: string, path: string, body?: string, query: IDataObject = {}, headers: IDataObject = {}, option: IDataObject = {}, region?: string): Promise { // tslint:disable-line:no-any
+
+ const returnData: IDataObject[] = [];
+
+ let responseData;
+
+ const propertyNameArray = propertyName.split('.');
+
+ do {
+ responseData = await awsApiRequestREST.call(this, service, method, path, body, query);
+
+ if (get(responseData, `${propertyNameArray[0]}.${propertyNameArray[1]}.NextToken`)) {
+ query['NextToken'] = get(responseData, `${propertyNameArray[0]}.${propertyNameArray[1]}.NextToken`);
+ }
+ if (get(responseData, propertyName)) {
+ if (Array.isArray(get(responseData, propertyName))) {
+ returnData.push.apply(returnData, get(responseData, propertyName));
+ } else {
+ returnData.push(get(responseData, propertyName));
+ }
+ }
+ } while (
+ get(responseData, `${propertyNameArray[0]}.${propertyNameArray[1]}.NextToken`) !== undefined
+ );
+
+ return returnData;
+}
+
diff --git a/packages/nodes-base/nodes/Aws/Transcribe/transcribe.svg b/packages/nodes-base/nodes/Aws/Transcribe/transcribe.svg
new file mode 100644
index 0000000000..d055f5e9c7
--- /dev/null
+++ b/packages/nodes-base/nodes/Aws/Transcribe/transcribe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 9c1a149ce6..6a11aa1f59 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -300,6 +300,7 @@
"dist/nodes/Aws/S3/AwsS3.node.js",
"dist/nodes/Aws/SES/AwsSes.node.js",
"dist/nodes/Aws/SQS/AwsSqs.node.js",
+ "dist/nodes/Aws/Transcribe/AwsTranscribe.node.js",
"dist/nodes/Aws/AwsSns.node.js",
"dist/nodes/Aws/AwsSnsTrigger.node.js",
"dist/nodes/Bannerbear/Bannerbear.node.js",