voicemix 1.3.4 → 1.3.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "voicemix",
3
3
  "type": "module",
4
- "version": "1.3.4",
4
+ "version": "1.3.6",
5
5
  "description": "🗣️ VoiceMix - A simple text-to-speech tool using ElevenLabs, Cartesia and Resemble AI APIs.",
6
6
  "main": "index.js",
7
7
  "repository": {
@@ -14,7 +14,6 @@
14
14
  "author": "Martin Clasen",
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
- "axios": "^1.13.4",
18
17
  "hash-factory": "^1.1.2"
19
18
  },
20
19
  "keywords": [
@@ -1,6 +1,6 @@
1
- import axios from 'axios';
2
1
  import fs from 'fs';
3
2
  import path from 'path';
3
+ import { Readable } from 'stream';
4
4
  import { pipeline } from 'stream/promises';
5
5
  import { ProviderError } from '../errors.js';
6
6
 
@@ -19,33 +19,31 @@ export class CartesiaProvider {
19
19
 
20
20
  }
21
21
 
22
- _getRequestOptions(voiceId, text, format = 'mp3') {
22
+ _getFetchParams(voiceId, text, format = 'mp3') {
23
23
  // Configure output format based on requested format
24
- const outputFormat = format === 'mp3'
24
+ const outputFormat = format === 'mp3'
25
25
  ? { container: 'mp3', encoding: 'mp3', sample_rate: 44100 }
26
26
  : { container: 'wav', encoding: 'pcm_f32le', sample_rate: 44100 };
27
27
 
28
- return {
29
- method: 'post',
30
- url: `${this.baseUrl}/tts/bytes`,
31
- headers: {
32
- 'Authorization': `Bearer ${this.apiKey}`,
33
- 'Cartesia-Version': '2024-06-10',
34
- 'Content-Type': 'application/json'
35
- },
36
- data: {
37
- model_id: this.defaultSettings.model_id,
38
- transcript: text,
39
- voice: {
40
- mode: 'id',
41
- id: voiceId
42
- },
43
- output_format: outputFormat,
44
- speed: this.defaultSettings.speed,
45
- generation_config: this.defaultSettings.generation_config
46
- },
47
- responseType: 'stream'
28
+ const url = `${this.baseUrl}/tts/bytes`;
29
+ const headers = {
30
+ 'Authorization': `Bearer ${this.apiKey}`,
31
+ 'Cartesia-Version': '2024-06-10',
32
+ 'Content-Type': 'application/json'
48
33
  };
34
+ const body = JSON.stringify({
35
+ model_id: this.defaultSettings.model_id,
36
+ transcript: text,
37
+ voice: {
38
+ mode: 'id',
39
+ id: voiceId
40
+ },
41
+ output_format: outputFormat,
42
+ speed: this.defaultSettings.speed,
43
+ generation_config: this.defaultSettings.generation_config
44
+ });
45
+
46
+ return { url, headers, body };
49
47
  }
50
48
 
51
49
  async save(voiceId, text, format, filePath, fileName) {
@@ -63,7 +61,17 @@ export class CartesiaProvider {
63
61
  throw new ProviderError('File path and name are required', 'cartesia');
64
62
  }
65
63
 
66
- const response = await axios(this._getRequestOptions(voiceId, text, format));
64
+ const { url, headers, body } = this._getFetchParams(voiceId, text, format);
65
+ const response = await fetch(url, { method: 'POST', headers, body });
66
+
67
+ if (!response.ok) {
68
+ const errorMessage = (await response.text().catch(() => '')) || 'Failed to save audio from Cartesia API';
69
+ throw new ProviderError(errorMessage, 'cartesia', {
70
+ status: response.status,
71
+ statusText: response.statusText,
72
+ message: errorMessage
73
+ });
74
+ }
67
75
 
68
76
  if (!fs.existsSync(filePath)) {
69
77
  fs.mkdirSync(filePath, { recursive: true });
@@ -73,7 +81,7 @@ export class CartesiaProvider {
73
81
  const writer = fs.createWriteStream(fullPath);
74
82
 
75
83
  try {
76
- await pipeline(response.data, writer);
84
+ await pipeline(Readable.fromWeb(response.body), writer);
77
85
  return fullPath;
78
86
  } catch (err) {
79
87
  // Clean up partial file on failure
@@ -90,40 +98,7 @@ export class CartesiaProvider {
90
98
  if (error instanceof ProviderError) {
91
99
  throw error;
92
100
  }
93
-
94
- if (axios.isAxiosError(error)) {
95
- // Try to read the error message from the response stream
96
- let errorMessage = 'Failed to save audio from Cartesia API';
97
-
98
- if (error.response?.data) {
99
- try {
100
- // If data is a stream/buffer, try to read it
101
- if (typeof error.response.data.read === 'function') {
102
- const errorData = error.response.data.read();
103
- if (errorData) {
104
- errorMessage = errorData.toString('utf-8');
105
- }
106
- } else if (typeof error.response.data === 'string') {
107
- errorMessage = error.response.data;
108
- }
109
- } catch (readError) {
110
- // Keep default error message
111
- }
112
- }
113
-
114
- const details = {
115
- status: error.response?.status,
116
- statusText: error.response?.statusText,
117
- message: errorMessage
118
- };
119
-
120
- throw new ProviderError(
121
- errorMessage,
122
- 'cartesia',
123
- details
124
- );
125
- }
126
-
101
+
127
102
  throw new ProviderError(
128
103
  'An unexpected error occurred while saving audio',
129
104
  'cartesia',
@@ -132,4 +107,3 @@ export class CartesiaProvider {
132
107
  }
133
108
  }
134
109
  }
135
-
@@ -1,6 +1,6 @@
1
- import axios from 'axios';
2
1
  import fs from 'fs';
3
2
  import path from 'path';
3
+ import { Readable } from 'stream';
4
4
  import { pipeline } from 'stream/promises';
5
5
  import { ProviderError } from '../errors.js';
6
6
 
@@ -39,24 +39,22 @@ export class ElevenLabsProvider {
39
39
  };
40
40
 
41
41
  return this;
42
- }
43
-
44
- _getRequestOptions(voiceId, text, format) {
45
- return {
46
- method: "post",
47
- url: `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
48
- headers: {
49
- accept: "audio/" + (format === "mp3" ? "mpeg" : format),
50
- "xi-api-key": this.apiKey,
51
- "Content-Type": "application/json",
52
- },
53
- data: {
54
- text,
55
- model_id: this.model_id,
56
- voice_settings: this.voice_settings,
57
- },
58
- responseType: "stream",
42
+ }
43
+
44
+ _getFetchParams(voiceId, text, format) {
45
+ const url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`;
46
+ const headers = {
47
+ accept: 'audio/' + (format === 'mp3' ? 'mpeg' : format),
48
+ 'xi-api-key': this.apiKey,
49
+ 'Content-Type': 'application/json',
59
50
  };
51
+ const body = JSON.stringify({
52
+ text,
53
+ model_id: this.model_id,
54
+ voice_settings: this.voice_settings,
55
+ });
56
+
57
+ return { url, headers, body };
60
58
  }
61
59
 
62
60
  async save(voiceId, text, format, filePath, fileName) {
@@ -74,10 +72,21 @@ export class ElevenLabsProvider {
74
72
  throw new ProviderError('File path and name are required', 'elevenlabs');
75
73
  }
76
74
 
77
- const response = await axios({
78
- ...this._getRequestOptions(voiceId, text, format),
79
- responseType: 'stream'
80
- });
75
+ const { url, headers, body } = this._getFetchParams(voiceId, text, format);
76
+ const response = await fetch(url, { method: 'POST', headers, body });
77
+
78
+ if (!response.ok) {
79
+ const errorBody = await response.text().catch(() => '');
80
+ throw new ProviderError(
81
+ 'Failed to save audio from ElevenLabs API',
82
+ 'elevenlabs',
83
+ {
84
+ status: response.status,
85
+ statusText: response.statusText,
86
+ data: errorBody,
87
+ }
88
+ );
89
+ }
81
90
 
82
91
  if (!fs.existsSync(filePath)) {
83
92
  fs.mkdirSync(filePath, { recursive: true });
@@ -87,7 +96,7 @@ export class ElevenLabsProvider {
87
96
  const writer = fs.createWriteStream(fullPath);
88
97
 
89
98
  try {
90
- await pipeline(response.data, writer);
99
+ await pipeline(Readable.fromWeb(response.body), writer);
91
100
  return fullPath;
92
101
  } catch (err) {
93
102
  // Clean up partial file on failure
@@ -104,21 +113,7 @@ export class ElevenLabsProvider {
104
113
  if (error instanceof ProviderError) {
105
114
  throw error;
106
115
  }
107
-
108
- if (axios.isAxiosError(error)) {
109
- const details = {
110
- status: error.response?.status,
111
- statusText: error.response?.statusText,
112
- data: error.response?.data
113
- };
114
-
115
- throw new ProviderError(
116
- 'Failed to save audio from ElevenLabs API',
117
- 'elevenlabs',
118
- details
119
- );
120
- }
121
-
116
+
122
117
  throw new ProviderError(
123
118
  'An unexpected error occurred while saving audio',
124
119
  'elevenlabs',
@@ -126,4 +121,4 @@ export class ElevenLabsProvider {
126
121
  );
127
122
  }
128
123
  }
129
- }
124
+ }
@@ -1,4 +1,3 @@
1
- import axios from 'axios';
2
1
  import fs from 'fs';
3
2
  import path from 'path';
4
3
  import { ProviderError, ValidationError } from '../errors.js';
@@ -15,17 +14,16 @@ export class ResembleProvider {
15
14
 
16
15
  }
17
16
 
18
- _getRequestOptions(endpoint, data) {
19
- return {
20
- method: 'post',
21
- url: `${this.baseUrl}${endpoint}`,
22
- headers: {
23
- 'Authorization': `Bearer ${this.apiKey}`,
24
- 'Content-Type': 'application/json',
25
- 'Accept-Encoding': 'gzip, deflate, br'
26
- },
27
- data
17
+ _getFetchParams(endpoint, data) {
18
+ const url = `${this.baseUrl}${endpoint}`;
19
+ const headers = {
20
+ 'Authorization': `Bearer ${this.apiKey}`,
21
+ 'Content-Type': 'application/json',
22
+ 'Accept-Encoding': 'gzip, deflate, br'
28
23
  };
24
+ const body = JSON.stringify(data);
25
+
26
+ return { url, headers, body };
29
27
  }
30
28
 
31
29
  async save(voiceId, text, format, filePath, fileName) {
@@ -43,19 +41,42 @@ export class ResembleProvider {
43
41
  throw new ProviderError('File path and name are required', 'resemble');
44
42
  }
45
43
 
46
- const response = await axios(this._getRequestOptions('/synthesize', {
44
+ const { url, headers, body } = this._getFetchParams('/synthesize', {
47
45
  voice_uuid: voiceId,
48
46
  data: text,
49
47
  sample_rate: this.defaultSettings.sample_rate,
50
48
  output_format: format,
51
49
  precision: this.defaultSettings.precision
52
- }));
50
+ });
51
+
52
+ const response = await fetch(url, { method: 'POST', headers, body });
53
+
54
+ if (!response.ok) {
55
+ let data;
56
+ const raw = await response.text().catch(() => '');
57
+ try {
58
+ data = raw ? JSON.parse(raw) : raw;
59
+ } catch {
60
+ data = raw;
61
+ }
62
+ throw new ProviderError(
63
+ 'Failed to save audio from Resemble API',
64
+ 'resemble',
65
+ {
66
+ status: response.status,
67
+ statusText: response.statusText,
68
+ data,
69
+ }
70
+ );
71
+ }
72
+
73
+ const responseData = await response.json();
53
74
 
54
- if (!response.data.success) {
75
+ if (!responseData.success) {
55
76
  throw new ProviderError(
56
- response.data.issues?.join(', ') || 'Synthesis failed',
77
+ responseData.issues?.join(', ') || 'Synthesis failed',
57
78
  'resemble',
58
- response.data
79
+ responseData
59
80
  );
60
81
  }
61
82
 
@@ -64,8 +85,8 @@ export class ResembleProvider {
64
85
  }
65
86
 
66
87
  const fullPath = path.join(filePath, fileName);
67
- const audioBuffer = Buffer.from(response.data.audio_content, 'base64');
68
-
88
+ const audioBuffer = Buffer.from(responseData.audio_content, 'base64');
89
+
69
90
  return new Promise((resolve, reject) => {
70
91
  fs.writeFile(fullPath, audioBuffer, (err) => {
71
92
  if (err) {
@@ -83,21 +104,7 @@ export class ResembleProvider {
83
104
  if (error instanceof ProviderError) {
84
105
  throw error;
85
106
  }
86
-
87
- if (axios.isAxiosError(error)) {
88
- const details = {
89
- status: error.response?.status,
90
- statusText: error.response?.statusText,
91
- data: error.response?.data
92
- };
93
-
94
- throw new ProviderError(
95
- 'Failed to save audio from Resemble API',
96
- 'resemble',
97
- details
98
- );
99
- }
100
-
107
+
101
108
  throw new ProviderError(
102
109
  'An unexpected error occurred while saving audio',
103
110
  'resemble',
@@ -135,4 +142,4 @@ export class ResembleProvider {
135
142
  this.defaultSettings.output_format = format;
136
143
  return this;
137
144
  }
138
- }
145
+ }