n8n-nodes-tts-bigboss 1.0.6 → 1.0.7

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.
@@ -125,7 +125,7 @@ class TTSBigBoss {
125
125
  {
126
126
  name: 'Coqui TTS (Local Server)',
127
127
  value: 'coqui',
128
- description: 'Connect to a running Coqui TTS/XTTS server (e.g. python3 coqui_server.py).',
128
+ description: 'Connect to a running Coqui TTS/XTTS server.',
129
129
  },
130
130
  {
131
131
  name: 'System Command (Custom)',
@@ -277,11 +277,11 @@ class TTSBigBoss {
277
277
  description: 'Name from Hugging Face (e.g. en_US-bryce-medium) or full URL to .onnx file.',
278
278
  },
279
279
  {
280
- displayName: 'Server URL',
280
+ displayName: 'Base Server URL',
281
281
  name: 'coquiUrl',
282
282
  type: 'string',
283
- default: 'http://localhost:5002/api/tts',
284
- description: 'URL of the running Coqui server endpoint (usually /api/tts or /tts_stream).',
283
+ default: 'http://host.docker.internal:5002',
284
+ description: 'Base URL of Coqui server (e.g. http://172.17.0.1:5002 if in Docker). Do not include /api/tts.',
285
285
  displayOptions: {
286
286
  show: {
287
287
  engine: ['coqui'],
@@ -289,23 +289,56 @@ class TTSBigBoss {
289
289
  },
290
290
  },
291
291
  {
292
- displayName: 'Speaker ID / Wav Path',
292
+ displayName: 'Speaker',
293
293
  name: 'coquiSpeaker',
294
+ type: 'options',
295
+ typeOptions: {
296
+ loadOptionsMethod: 'getCoquiSpeakers',
297
+ loadOptionsDependsOn: ['coquiUrl'],
298
+ },
299
+ default: '',
300
+ description: 'Select a speaker ID loaded from the server.',
301
+ displayOptions: {
302
+ show: {
303
+ engine: ['coqui'],
304
+ },
305
+ },
306
+ },
307
+ {
308
+ displayName: 'Use Custom WAV Path',
309
+ name: 'coquiUseWav',
310
+ type: 'boolean',
311
+ default: false,
312
+ description: 'Check to use a local WAV file path instead of a Speaker ID (for cloning).',
313
+ displayOptions: {
314
+ show: {
315
+ engine: ['coqui'],
316
+ },
317
+ },
318
+ },
319
+ {
320
+ displayName: 'WAV Path',
321
+ name: 'coquiWavPath',
294
322
  type: 'string',
295
323
  default: '',
296
- description: 'Speaker ID (if multi-speaker) or path to reference wav (for cloning).',
324
+ description: 'Absolute path to the reference WAV file on the server.',
297
325
  displayOptions: {
298
326
  show: {
299
327
  engine: ['coqui'],
328
+ coquiUseWav: [true],
300
329
  },
301
330
  },
302
331
  },
303
332
  {
304
333
  displayName: 'Language',
305
334
  name: 'coquiLang',
306
- type: 'string',
335
+ type: 'options',
336
+ typeOptions: {
337
+ loadOptionsMethod: 'getCoquiLanguages',
338
+ loadOptionsDependsOn: ['coquiUrl'],
339
+ },
307
340
  default: 'en',
308
- description: 'Language code (e.g. en, ar, fr).',
341
+ description: 'Select language.',
309
342
  displayOptions: {
310
343
  show: {
311
344
  engine: ['coqui'],
@@ -314,6 +347,60 @@ class TTSBigBoss {
314
347
  },
315
348
  ],
316
349
  };
350
+ this.methods = {
351
+ loadOptions: {
352
+ async getCoquiSpeakers() {
353
+ const baseUrl = this.getNodeParameter('coquiUrl');
354
+ const cleanUrl = baseUrl.replace(/\/$/, '');
355
+ const targetUrl = `${cleanUrl}/api/speakers`;
356
+ try {
357
+ const data = await httpRequest(targetUrl);
358
+ const json = JSON.parse(data.toString());
359
+ let speakers = [];
360
+ if (Array.isArray(json))
361
+ speakers = json;
362
+ else if (json.speakers)
363
+ speakers = json.speakers;
364
+ else if (typeof json === 'object')
365
+ speakers = Object.keys(json);
366
+ return speakers.map((s) => {
367
+ const name = typeof s === 'string' ? s : (s.name || s.id);
368
+ const value = typeof s === 'string' ? s : (s.id || s.name);
369
+ return { name, value };
370
+ });
371
+ }
372
+ catch (e) {
373
+ return [{ name: `Error loading: ${e.message}. Check URL & Connection.`, value: '' }];
374
+ }
375
+ },
376
+ async getCoquiLanguages() {
377
+ const baseUrl = this.getNodeParameter('coquiUrl');
378
+ const cleanUrl = baseUrl.replace(/\/$/, '');
379
+ const targetUrl = `${cleanUrl}/api/languages`;
380
+ try {
381
+ const data = await httpRequest(targetUrl);
382
+ const json = JSON.parse(data.toString());
383
+ let langs = [];
384
+ if (Array.isArray(json))
385
+ langs = json;
386
+ else if (json.languages)
387
+ langs = json.languages;
388
+ return langs.map((l) => {
389
+ const name = typeof l === 'string' ? l : (l.name || l.code);
390
+ const value = typeof l === 'string' ? l : (l.code || l.name);
391
+ return { name, value };
392
+ });
393
+ }
394
+ catch (e) {
395
+ return [
396
+ { name: 'English (en)', value: 'en' },
397
+ { name: 'Arabic (ar)', value: 'ar' },
398
+ { name: 'Examples (Fix URL to load)', value: 'en' }
399
+ ];
400
+ }
401
+ },
402
+ },
403
+ };
317
404
  }
318
405
  async execute() {
319
406
  const items = this.getInputData();
@@ -384,38 +471,23 @@ class TTSBigBoss {
384
471
  fs.unlinkSync(outFile);
385
472
  }
386
473
  else if (engine === 'coqui') {
387
- const url = this.getNodeParameter('coquiUrl', i);
388
- const speaker = this.getNodeParameter('coquiSpeaker', i);
474
+ let url = this.getNodeParameter('coquiUrl', i);
475
+ url = url.replace(/\/$/, '') + '/api/tts';
476
+ const speakerSelection = this.getNodeParameter('coquiSpeaker', i);
477
+ const useWav = this.getNodeParameter('coquiUseWav', i, false);
478
+ const wavPath = this.getNodeParameter('coquiWavPath', i, '');
389
479
  const lang = this.getNodeParameter('coquiLang', i);
390
480
  const payload = {
391
481
  text: text,
392
482
  language_id: lang,
393
483
  };
394
- if (speaker)
395
- payload.speaker_id = speaker;
396
- const requestModule = url.startsWith('https') ? https : http;
397
- audioBuffer = await new Promise((resolve, reject) => {
398
- const req = requestModule.request(url, {
399
- method: 'POST',
400
- headers: {
401
- 'Content-Type': 'application/json',
402
- }
403
- }, (res) => {
404
- const chunks = [];
405
- res.on('data', (d) => chunks.push(d));
406
- res.on('end', () => {
407
- if (res.statusCode >= 200 && res.statusCode < 300) {
408
- resolve(Buffer.concat(chunks));
409
- }
410
- else {
411
- reject(new Error(`Coqui Server Error ${res.statusCode}: ${Buffer.concat(chunks).toString()}`));
412
- }
413
- });
414
- });
415
- req.on('error', reject);
416
- req.write(JSON.stringify(payload));
417
- req.end();
418
- });
484
+ if (useWav && wavPath) {
485
+ payload.speaker_wav = wavPath;
486
+ }
487
+ else if (speakerSelection) {
488
+ payload.speaker_id = speakerSelection;
489
+ }
490
+ audioBuffer = await httpRequest(url, 'POST', payload);
419
491
  srtBuffer = Buffer.from(generateHeuristicSRT(text, audioBuffer.length), 'utf8');
420
492
  }
421
493
  else {
@@ -481,7 +553,7 @@ async function runEdgeTTS(text, voice, rate, pitch) {
481
553
  return new Promise((resolve, reject) => {
482
554
  const ws = new ws_1.default(EDGE_URL, {
483
555
  headers: {
484
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
556
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0',
485
557
  'Origin': 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold',
486
558
  'TrustedClientToken': '6A5AA1D4EAFF4E9FB37E23D68491D6F4'
487
559
  }
@@ -736,3 +808,29 @@ async function downloadFile(url, dest) {
736
808
  });
737
809
  });
738
810
  }
811
+ async function httpRequest(url, method = 'GET', body = null) {
812
+ const requestModule = url.startsWith('https') ? https : http;
813
+ return new Promise((resolve, reject) => {
814
+ const req = requestModule.request(url, {
815
+ method: method,
816
+ headers: {
817
+ 'Content-Type': 'application/json',
818
+ }
819
+ }, (res) => {
820
+ const chunks = [];
821
+ res.on('data', (d) => chunks.push(d));
822
+ res.on('end', () => {
823
+ if (res.statusCode >= 200 && res.statusCode < 300) {
824
+ resolve(Buffer.concat(chunks));
825
+ }
826
+ else {
827
+ reject(new Error(`Server Request Failed ${res.statusCode}: ${Buffer.concat(chunks).toString()}`));
828
+ }
829
+ });
830
+ });
831
+ req.on('error', reject);
832
+ if (body)
833
+ req.write(JSON.stringify(body));
834
+ req.end();
835
+ });
836
+ }
@@ -3,6 +3,8 @@ import {
3
3
  INodeExecutionData,
4
4
  INodeType,
5
5
  INodeTypeDescription,
6
+ ILoadOptionsFunctions,
7
+ INodePropertyOptions,
6
8
  } from 'n8n-workflow';
7
9
  import { v4 as uuidv4 } from 'uuid';
8
10
  import * as fs from 'fs';
@@ -123,7 +125,7 @@ export class TTSBigBoss implements INodeType {
123
125
  {
124
126
  name: 'Coqui TTS (Local Server)',
125
127
  value: 'coqui',
126
- description: 'Connect to a running Coqui TTS/XTTS server (e.g. python3 coqui_server.py).',
128
+ description: 'Connect to a running Coqui TTS/XTTS server.',
127
129
  },
128
130
  {
129
131
  name: 'System Command (Custom)',
@@ -290,11 +292,11 @@ export class TTSBigBoss implements INodeType {
290
292
  // Coqui Server Settings
291
293
  // ----------------------------------
292
294
  {
293
- displayName: 'Server URL',
295
+ displayName: 'Base Server URL',
294
296
  name: 'coquiUrl',
295
297
  type: 'string',
296
- default: 'http://localhost:5002/api/tts',
297
- description: 'URL of the running Coqui server endpoint (usually /api/tts or /tts_stream).',
298
+ default: 'http://host.docker.internal:5002',
299
+ description: 'Base URL of Coqui server (e.g. http://172.17.0.1:5002 if in Docker). Do not include /api/tts.',
298
300
  displayOptions: {
299
301
  show: {
300
302
  engine: ['coqui'],
@@ -302,23 +304,56 @@ export class TTSBigBoss implements INodeType {
302
304
  },
303
305
  },
304
306
  {
305
- displayName: 'Speaker ID / Wav Path',
307
+ displayName: 'Speaker',
306
308
  name: 'coquiSpeaker',
309
+ type: 'options',
310
+ typeOptions: {
311
+ loadOptionsMethod: 'getCoquiSpeakers',
312
+ loadOptionsDependsOn: ['coquiUrl'],
313
+ },
314
+ default: '',
315
+ description: 'Select a speaker ID loaded from the server.',
316
+ displayOptions: {
317
+ show: {
318
+ engine: ['coqui'],
319
+ },
320
+ },
321
+ },
322
+ {
323
+ displayName: 'Use Custom WAV Path',
324
+ name: 'coquiUseWav',
325
+ type: 'boolean',
326
+ default: false,
327
+ description: 'Check to use a local WAV file path instead of a Speaker ID (for cloning).',
328
+ displayOptions: {
329
+ show: {
330
+ engine: ['coqui'],
331
+ },
332
+ },
333
+ },
334
+ {
335
+ displayName: 'WAV Path',
336
+ name: 'coquiWavPath',
307
337
  type: 'string',
308
338
  default: '',
309
- description: 'Speaker ID (if multi-speaker) or path to reference wav (for cloning).',
339
+ description: 'Absolute path to the reference WAV file on the server.',
310
340
  displayOptions: {
311
341
  show: {
312
342
  engine: ['coqui'],
343
+ coquiUseWav: [true],
313
344
  },
314
345
  },
315
346
  },
316
347
  {
317
348
  displayName: 'Language',
318
349
  name: 'coquiLang',
319
- type: 'string',
350
+ type: 'options',
351
+ typeOptions: {
352
+ loadOptionsMethod: 'getCoquiLanguages',
353
+ loadOptionsDependsOn: ['coquiUrl'],
354
+ },
320
355
  default: 'en',
321
- description: 'Language code (e.g. en, ar, fr).',
356
+ description: 'Select language.',
322
357
  displayOptions: {
323
358
  show: {
324
359
  engine: ['coqui'],
@@ -328,6 +363,63 @@ export class TTSBigBoss implements INodeType {
328
363
  ],
329
364
  };
330
365
 
366
+ methods = {
367
+ loadOptions: {
368
+ async getCoquiSpeakers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
369
+ const baseUrl = this.getNodeParameter('coquiUrl') as string;
370
+ // clean url
371
+ const cleanUrl = baseUrl.replace(/\/$/, '');
372
+ const targetUrl = `${cleanUrl}/api/speakers`; // Assumption: endpoints exist
373
+
374
+ try {
375
+ const data = await httpRequest(targetUrl);
376
+ // Assume data is [ {name: "id", ...} ] or [ "id", "id" ] or { "speakers": [...] }
377
+ const json = JSON.parse(data.toString());
378
+ let speakers: any[] = [];
379
+
380
+ if (Array.isArray(json)) speakers = json;
381
+ else if (json.speakers) speakers = json.speakers;
382
+ else if (typeof json === 'object') speakers = Object.keys(json);
383
+
384
+ return speakers.map((s: any) => {
385
+ const name = typeof s === 'string' ? s : (s.name || s.id);
386
+ const value = typeof s === 'string' ? s : (s.id || s.name);
387
+ return { name, value };
388
+ });
389
+ } catch (e: any) {
390
+ return [{ name: `Error loading: ${e.message}. Check URL & Connection.`, value: '' }];
391
+ }
392
+ },
393
+ async getCoquiLanguages(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
394
+ const baseUrl = this.getNodeParameter('coquiUrl') as string;
395
+ const cleanUrl = baseUrl.replace(/\/$/, '');
396
+ const targetUrl = `${cleanUrl}/api/languages`;
397
+
398
+ try {
399
+ const data = await httpRequest(targetUrl);
400
+ const json = JSON.parse(data.toString());
401
+ let langs: any[] = [];
402
+
403
+ if (Array.isArray(json)) langs = json;
404
+ else if (json.languages) langs = json.languages;
405
+
406
+ return langs.map((l: any) => {
407
+ const name = typeof l === 'string' ? l : (l.name || l.code);
408
+ const value = typeof l === 'string' ? l : (l.code || l.name);
409
+ return { name, value };
410
+ });
411
+ } catch (e) {
412
+ // Fallback defaults if api fails
413
+ return [
414
+ { name: 'English (en)', value: 'en' },
415
+ { name: 'Arabic (ar)', value: 'ar' },
416
+ { name: 'Examples (Fix URL to load)', value: 'en' }
417
+ ];
418
+ }
419
+ },
420
+ },
421
+ };
422
+
331
423
  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
332
424
  const items = this.getInputData();
333
425
  const returnData: INodeExecutionData[] = [];
@@ -426,43 +518,28 @@ export class TTSBigBoss implements INodeType {
426
518
  // ----------------------------------
427
519
  // COQUI SEVER EXECUTION
428
520
  // ----------------------------------
429
- const url = this.getNodeParameter('coquiUrl', i) as string;
430
- const speaker = this.getNodeParameter('coquiSpeaker', i) as string;
521
+ let url = this.getNodeParameter('coquiUrl', i) as string;
522
+ url = url.replace(/\/$/, '') + '/api/tts'; // Append standard endpoint
523
+
524
+ const speakerSelection = this.getNodeParameter('coquiSpeaker', i) as string;
525
+ const useWav = this.getNodeParameter('coquiUseWav', i, false) as boolean;
526
+ const wavPath = this.getNodeParameter('coquiWavPath', i, '') as string;
431
527
  const lang = this.getNodeParameter('coquiLang', i) as string;
432
528
 
433
529
  // Construct Payload
434
- // Standard XTTS/Coqui API expects: text, speaker_id, language_id
435
530
  const payload: any = {
436
531
  text: text,
437
532
  language_id: lang,
438
533
  };
439
- if (speaker) payload.speaker_id = speaker;
440
-
441
- // Allow http and https
442
- const requestModule = url.startsWith('https') ? https : http;
443
534
 
444
- audioBuffer = await new Promise((resolve, reject) => {
445
- const req = requestModule.request(url, {
446
- method: 'POST',
447
- headers: {
448
- 'Content-Type': 'application/json',
449
- }
450
- }, (res: any) => {
451
- const chunks: any[] = [];
452
- res.on('data', (d: any) => chunks.push(d));
453
- res.on('end', () => {
454
- if (res.statusCode >= 200 && res.statusCode < 300) {
455
- resolve(Buffer.concat(chunks));
456
- } else {
457
- reject(new Error(`Coqui Server Error ${res.statusCode}: ${Buffer.concat(chunks).toString()}`));
458
- }
459
- });
460
- });
461
- req.on('error', reject);
462
- req.write(JSON.stringify(payload));
463
- req.end();
464
- });
535
+ if (useWav && wavPath) {
536
+ payload.speaker_wav = wavPath;
537
+ } else if (speakerSelection) {
538
+ payload.speaker_id = speakerSelection;
539
+ }
465
540
 
541
+ // Execute Request
542
+ audioBuffer = await httpRequest(url, 'POST', payload);
466
543
  srtBuffer = Buffer.from(generateHeuristicSRT(text, audioBuffer.length), 'utf8');
467
544
 
468
545
  } else {
@@ -540,7 +617,7 @@ export class TTSBigBoss implements INodeType {
540
617
 
541
618
  returnData.push(newItem);
542
619
 
543
- } catch (error) {
620
+ } catch (error: any) {
544
621
  if (this.continueOnFail()) {
545
622
  returnData.push({ json: { error: error.message }, binary: {} });
546
623
  continue;
@@ -560,8 +637,8 @@ async function runEdgeTTS(text: string, voice: string, rate: string, pitch: stri
560
637
  return new Promise((resolve, reject) => {
561
638
  const ws = new WebSocket(EDGE_URL, {
562
639
  headers: {
563
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
564
- 'Origin': 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold',
640
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0', // Updated UA to Edge
641
+ 'Origin': 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold', // Keep origin for now, usually required
565
642
  'TrustedClientToken': '6A5AA1D4EAFF4E9FB37E23D68491D6F4'
566
643
  }
567
644
  });
@@ -907,3 +984,28 @@ async function downloadFile(url: string, dest: string): Promise<void> {
907
984
  });
908
985
  });
909
986
  }
987
+
988
+ async function httpRequest(url: string, method: string = 'GET', body: any = null): Promise<Buffer> {
989
+ const requestModule = url.startsWith('https') ? https : http;
990
+ return new Promise((resolve, reject) => {
991
+ const req = requestModule.request(url, {
992
+ method: method,
993
+ headers: {
994
+ 'Content-Type': 'application/json',
995
+ }
996
+ }, (res: any) => {
997
+ const chunks: any[] = [];
998
+ res.on('data', (d: any) => chunks.push(d));
999
+ res.on('end', () => {
1000
+ if (res.statusCode >= 200 && res.statusCode < 300) {
1001
+ resolve(Buffer.concat(chunks));
1002
+ } else {
1003
+ reject(new Error(`Server Request Failed ${res.statusCode}: ${Buffer.concat(chunks).toString()}`));
1004
+ }
1005
+ });
1006
+ });
1007
+ req.on('error', reject);
1008
+ if (body) req.write(JSON.stringify(body));
1009
+ req.end();
1010
+ });
1011
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tts-bigboss",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "BigBoss TTS node with multi-engine support and automatic SRT generation",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",