@cartesia/cartesia-js 1.0.3 → 1.2.1-alpha.2

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +32 -3
  3. package/dist/{chunk-BHY7MNGT.js → chunk-5SBAQNWQ.js} +1 -1
  4. package/dist/{chunk-LZO6K34D.js → chunk-CSOXALSC.js} +7 -7
  5. package/dist/{chunk-PISCPZK4.js → chunk-FLWYXP5Z.js} +1 -1
  6. package/dist/chunk-GLZYI5DM.js +43 -0
  7. package/dist/{chunk-6YQ6KDIQ.js → chunk-I5YVYTNK.js} +47 -16
  8. package/dist/{chunk-NQVZNVOU.js → chunk-LKKWJLUG.js} +1 -1
  9. package/dist/{chunk-GHY2WEOK.js → chunk-NJDRWDQ3.js} +8 -35
  10. package/dist/{chunk-ZF6HASZT.js → chunk-QWNB544W.js} +7 -3
  11. package/dist/{chunk-YFN6TFR4.js → chunk-RJICGVPL.js} +12 -4
  12. package/dist/chunk-WLEVU3HN.js +42 -0
  13. package/dist/{chunk-EUW2435M.js → chunk-WSIVWXEI.js} +49 -25
  14. package/dist/index.cjs +162 -76
  15. package/dist/index.d.cts +2 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.js +10 -9
  18. package/dist/lib/client.js +2 -2
  19. package/dist/lib/constants.js +1 -1
  20. package/dist/lib/index.cjs +157 -71
  21. package/dist/lib/index.d.cts +2 -0
  22. package/dist/lib/index.d.ts +2 -0
  23. package/dist/lib/index.js +9 -8
  24. package/dist/react/index.cjs +165 -76
  25. package/dist/react/index.js +14 -10
  26. package/dist/react/utils.js +2 -2
  27. package/dist/tts/index.cjs +114 -69
  28. package/dist/tts/index.d.cts +8 -1
  29. package/dist/tts/index.d.ts +8 -1
  30. package/dist/tts/index.js +6 -6
  31. package/dist/tts/player.cjs +13 -27
  32. package/dist/tts/player.js +4 -4
  33. package/dist/tts/source.cjs +54 -37
  34. package/dist/tts/source.d.cts +9 -0
  35. package/dist/tts/source.d.ts +9 -0
  36. package/dist/tts/source.js +2 -2
  37. package/dist/tts/utils.js +3 -3
  38. package/dist/tts/websocket.cjs +99 -69
  39. package/dist/tts/websocket.d.cts +15 -9
  40. package/dist/tts/websocket.d.ts +15 -9
  41. package/dist/tts/websocket.js +5 -5
  42. package/dist/types/index.d.cts +42 -3
  43. package/dist/types/index.d.ts +42 -3
  44. package/dist/voice-changer/index.cjs +143 -0
  45. package/dist/voice-changer/index.d.cts +9 -0
  46. package/dist/voice-changer/index.d.ts +9 -0
  47. package/dist/voice-changer/index.js +9 -0
  48. package/dist/voices/index.cjs +10 -2
  49. package/dist/voices/index.d.cts +2 -1
  50. package/dist/voices/index.d.ts +2 -1
  51. package/dist/voices/index.js +3 -3
  52. package/package.json +8 -16
  53. package/src/lib/index.ts +3 -0
  54. package/src/react/index.ts +4 -0
  55. package/src/tts/index.ts +16 -1
  56. package/src/tts/source.ts +35 -0
  57. package/src/tts/websocket.ts +33 -2
  58. package/src/types/index.ts +60 -2
  59. package/src/voice-changer/index.ts +37 -0
  60. package/src/voices/index.ts +12 -3
  61. package/.turbo/turbo-build.log +0 -75
  62. package/dist/chunk-NWCW6C7H.js +0 -25
  63. package/tsconfig.json +0 -3
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __export = (target, all) => {
26
+ for (var name in all)
27
+ __defProp(target, name, { get: all[name], enumerable: true });
28
+ };
29
+ var __copyProps = (to, from, except, desc) => {
30
+ if (from && typeof from === "object" || typeof from === "function") {
31
+ for (let key of __getOwnPropNames(from))
32
+ if (!__hasOwnProp.call(to, key) && key !== except)
33
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
34
+ }
35
+ return to;
36
+ };
37
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
38
+ // If the importer is in node compatibility mode or this is not an ESM
39
+ // file that has been converted to a CommonJS file using a Babel-
40
+ // compatible transform (i.e. "__esModule" has not been set), then set
41
+ // "default" to the CommonJS "module.exports" for node compatibility.
42
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
43
+ mod
44
+ ));
45
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
46
+ var __async = (__this, __arguments, generator) => {
47
+ return new Promise((resolve, reject) => {
48
+ var fulfilled = (value) => {
49
+ try {
50
+ step(generator.next(value));
51
+ } catch (e) {
52
+ reject(e);
53
+ }
54
+ };
55
+ var rejected = (value) => {
56
+ try {
57
+ step(generator.throw(value));
58
+ } catch (e) {
59
+ reject(e);
60
+ }
61
+ };
62
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
63
+ step((generator = generator.apply(__this, __arguments)).next());
64
+ });
65
+ };
66
+
67
+ // src/voice-changer/index.ts
68
+ var voice_changer_exports = {};
69
+ __export(voice_changer_exports, {
70
+ default: () => VoiceChanger
71
+ });
72
+ module.exports = __toCommonJS(voice_changer_exports);
73
+
74
+ // src/lib/client.ts
75
+ var import_cross_fetch = __toESM(require("cross-fetch"), 1);
76
+
77
+ // src/lib/constants.ts
78
+ var BASE_URL = "https://api.cartesia.ai";
79
+ var CARTESIA_VERSION = "2024-06-10";
80
+ var constructApiUrl = (baseUrl, path, { websocket = false } = {}) => {
81
+ const url = new URL(path, baseUrl);
82
+ if (websocket) {
83
+ url.protocol = baseUrl.replace(/^http/, "ws");
84
+ }
85
+ return url;
86
+ };
87
+
88
+ // src/lib/client.ts
89
+ var Client = class {
90
+ constructor(options = {}) {
91
+ const apiKey = options.apiKey || process.env.CARTESIA_API_KEY;
92
+ if (!apiKey) {
93
+ throw new Error("Missing Cartesia API key.");
94
+ }
95
+ this.apiKey = typeof apiKey === "function" ? apiKey : () => __async(this, null, function* () {
96
+ return apiKey;
97
+ });
98
+ this.baseUrl = options.baseUrl || BASE_URL;
99
+ }
100
+ _fetch(_0) {
101
+ return __async(this, arguments, function* (path, options = {}) {
102
+ const url = constructApiUrl(this.baseUrl, path);
103
+ const headers = new Headers(options.headers);
104
+ headers.set("X-API-Key", yield this.apiKey());
105
+ headers.set("Cartesia-Version", CARTESIA_VERSION);
106
+ return (0, import_cross_fetch.default)(url.toString(), __spreadProps(__spreadValues({}, options), {
107
+ headers
108
+ }));
109
+ });
110
+ }
111
+ };
112
+
113
+ // src/voice-changer/index.ts
114
+ var VoiceChanger = class extends Client {
115
+ bytes(options) {
116
+ return __async(this, null, function* () {
117
+ const formData = new FormData();
118
+ formData.append("clip", options.clip);
119
+ formData.append("voice[id]", options.voice.id);
120
+ const fmt = options.output_format;
121
+ formData.append("output_format[container]", fmt.container);
122
+ if ("encoding" in fmt) {
123
+ formData.append("output_format[encoding]", fmt.encoding);
124
+ }
125
+ if ("bit_rate" in fmt) {
126
+ formData.append("output_format[bit_rate]", fmt.bit_rate.toString());
127
+ }
128
+ if ("sample_rate" in fmt) {
129
+ formData.append("output_format[sample_rate]", fmt.sample_rate.toString());
130
+ }
131
+ const response = yield this._fetch("/voice-changer/bytes", {
132
+ method: "POST",
133
+ body: formData
134
+ });
135
+ if (!response.ok) {
136
+ throw new Error(
137
+ `Voice changer error! status: ${response.status}, message: ${yield response.text()}`
138
+ );
139
+ }
140
+ return { buffer: yield response.arrayBuffer() };
141
+ });
142
+ }
143
+ };
@@ -0,0 +1,9 @@
1
+ import { Client } from '../lib/client.cjs';
2
+ import { VoiceChangerOptions, VoiceChangerBytesResponse } from '../types/index.cjs';
3
+ import 'emittery';
4
+
5
+ declare class VoiceChanger extends Client {
6
+ bytes(options: VoiceChangerOptions): Promise<VoiceChangerBytesResponse>;
7
+ }
8
+
9
+ export { VoiceChanger as default };
@@ -0,0 +1,9 @@
1
+ import { Client } from '../lib/client.js';
2
+ import { VoiceChangerOptions, VoiceChangerBytesResponse } from '../types/index.js';
3
+ import 'emittery';
4
+
5
+ declare class VoiceChanger extends Client {
6
+ bytes(options: VoiceChangerOptions): Promise<VoiceChangerBytesResponse>;
7
+ }
8
+
9
+ export { VoiceChanger as default };
@@ -0,0 +1,9 @@
1
+ import {
2
+ VoiceChanger
3
+ } from "../chunk-WLEVU3HN.js";
4
+ import "../chunk-FLWYXP5Z.js";
5
+ import "../chunk-2BFEKY3F.js";
6
+ import "../chunk-NJDRWDQ3.js";
7
+ export {
8
+ VoiceChanger as default
9
+ };
@@ -161,10 +161,18 @@ var Voices = class extends Client {
161
161
  }
162
162
  mix(options) {
163
163
  return __async(this, null, function* () {
164
- const request = options;
165
164
  const response = yield this._fetch("/voices/mix", {
166
165
  method: "POST",
167
- body: JSON.stringify(request)
166
+ body: JSON.stringify(options)
167
+ });
168
+ return response.json();
169
+ });
170
+ }
171
+ localize(options) {
172
+ return __async(this, null, function* () {
173
+ const response = yield this._fetch("/voices/localize", {
174
+ method: "POST",
175
+ body: JSON.stringify(options)
168
176
  });
169
177
  return response.json();
170
178
  });
@@ -1,5 +1,5 @@
1
1
  import { Client } from '../lib/client.cjs';
2
- import { Voice, CreateVoice, UpdateVoice, CloneOptions, CloneResponse, MixVoicesOptions, MixVoicesResponse } from '../types/index.cjs';
2
+ import { Voice, CreateVoice, UpdateVoice, CloneOptions, CloneResponse, MixVoicesOptions, MixVoicesResponse, LocalizeOptions, LocalizeResponse } from '../types/index.cjs';
3
3
  import 'emittery';
4
4
 
5
5
  declare class Voices extends Client {
@@ -9,6 +9,7 @@ declare class Voices extends Client {
9
9
  update(id: string, voice: UpdateVoice): Promise<Voice>;
10
10
  clone(options: CloneOptions): Promise<CloneResponse>;
11
11
  mix(options: MixVoicesOptions): Promise<MixVoicesResponse>;
12
+ localize(options: LocalizeOptions): Promise<LocalizeResponse>;
12
13
  }
13
14
 
14
15
  export { Voices as default };
@@ -1,5 +1,5 @@
1
1
  import { Client } from '../lib/client.js';
2
- import { Voice, CreateVoice, UpdateVoice, CloneOptions, CloneResponse, MixVoicesOptions, MixVoicesResponse } from '../types/index.js';
2
+ import { Voice, CreateVoice, UpdateVoice, CloneOptions, CloneResponse, MixVoicesOptions, MixVoicesResponse, LocalizeOptions, LocalizeResponse } from '../types/index.js';
3
3
  import 'emittery';
4
4
 
5
5
  declare class Voices extends Client {
@@ -9,6 +9,7 @@ declare class Voices extends Client {
9
9
  update(id: string, voice: UpdateVoice): Promise<Voice>;
10
10
  clone(options: CloneOptions): Promise<CloneResponse>;
11
11
  mix(options: MixVoicesOptions): Promise<MixVoicesResponse>;
12
+ localize(options: LocalizeOptions): Promise<LocalizeResponse>;
12
13
  }
13
14
 
14
15
  export { Voices as default };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  Voices
3
- } from "../chunk-YFN6TFR4.js";
4
- import "../chunk-PISCPZK4.js";
3
+ } from "../chunk-RJICGVPL.js";
4
+ import "../chunk-FLWYXP5Z.js";
5
5
  import "../chunk-2BFEKY3F.js";
6
- import "../chunk-GHY2WEOK.js";
6
+ import "../chunk-NJDRWDQ3.js";
7
7
  export {
8
8
  Voices as default
9
9
  };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "Cartesia",
5
5
  "url": "https://cartesia.ai"
6
6
  },
7
- "version": "1.0.3",
7
+ "version": "1.2.1-alpha.2",
8
8
  "description": "Client for the Cartesia API.",
9
9
  "type": "module",
10
10
  "module": "./dist/index.js",
@@ -27,7 +27,8 @@
27
27
  "cross-fetch": "^4.0.0",
28
28
  "emittery": "^1.0.3",
29
29
  "human-id": "^4.1.1",
30
- "partysocket": "^1.0.1"
30
+ "partysocket": "^1.0.1",
31
+ "react": "^18.3.1"
31
32
  },
32
33
  "publishConfig": {
33
34
  "access": "public"
@@ -36,20 +37,11 @@
36
37
  "build": "tsup src/ --format cjs,esm --dts",
37
38
  "dev": "bun run build -- --watch"
38
39
  },
39
- "peerDependencies": {
40
- "react": "^18.2.0",
41
- "@types/react": "^18.2.58"
42
- },
43
- "peerDependenciesMeta": {
44
- "react": {
45
- "optional": true
46
- },
47
- "@types/react": {
48
- "optional": true
49
- }
50
- },
51
40
  "devDependencies": {
52
- "@repo/config-typescript": "workspace:*",
53
- "tsup": "^8.0.2"
41
+ "@biomejs/biome": "^1.9.4",
42
+ "@types/node": "^22.7.9",
43
+ "@types/react": "^18.3.12",
44
+ "tsup": "^8.0.2",
45
+ "typescript": "^5.6.3"
54
46
  }
55
47
  }
package/src/lib/index.ts CHANGED
@@ -1,16 +1,19 @@
1
1
  import TTS from "../tts";
2
2
  import type { ClientOptions } from "../types";
3
+ import VoiceChanger from "../voice-changer";
3
4
  import Voices from "../voices";
4
5
  import { Client } from "./client";
5
6
 
6
7
  export class Cartesia extends Client {
7
8
  tts: TTS;
8
9
  voices: Voices;
10
+ voiceChanger: VoiceChanger;
9
11
 
10
12
  constructor(options: ClientOptions = {}) {
11
13
  super(options);
12
14
 
13
15
  this.tts = new TTS(options);
14
16
  this.voices = new Voices(options);
17
+ this.voiceChanger = new VoiceChanger(options);
15
18
  }
16
19
  }
@@ -201,6 +201,10 @@ export function useTTS({
201
201
  await player.current.stop();
202
202
  }
203
203
 
204
+ if (playbackStatus === "finished") {
205
+ websocketReturn.current.source.seek(0, "start");
206
+ }
207
+
204
208
  setPlaybackStatus("playing");
205
209
 
206
210
  const unsubscribes = [];
package/src/tts/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Client } from "../lib/client";
2
- import type { WebSocketOptions } from "../types";
2
+ import type { BytesRequest, WebSocketOptions } from "../types";
3
3
  import WebSocket from "./websocket";
4
4
 
5
5
  export default class TTS extends Client {
@@ -14,4 +14,19 @@ export default class TTS extends Client {
14
14
  baseUrl: this.baseUrl,
15
15
  });
16
16
  }
17
+
18
+ /**
19
+ * Generate audio bytes from text.
20
+ *
21
+ * @param options - The options for the request.
22
+ * @returns {Promise<ArrayBuffer>} A promise that resolves to an ArrayBuffer containing the audio bytes.
23
+ */
24
+ async bytes(options: BytesRequest): Promise<ArrayBuffer> {
25
+ const response = await this._fetch("/tts/bytes", {
26
+ method: "POST",
27
+ body: JSON.stringify(options),
28
+ });
29
+
30
+ return response.arrayBuffer();
31
+ }
17
32
  }
package/src/tts/source.ts CHANGED
@@ -124,6 +124,41 @@ export default class Source {
124
124
  return read;
125
125
  }
126
126
 
127
+ /**
128
+ * Seek in the buffer.
129
+ *
130
+ * @param offset The offset to seek to.
131
+ * @param whence The position to seek from.
132
+ * @returns The new position in the buffer.
133
+ * @throws {Error} If the seek is invalid.
134
+ */
135
+ async seek(
136
+ offset: number,
137
+ whence: "start" | "current" | "end",
138
+ ): Promise<number> {
139
+ let position = this.#readIndex;
140
+ switch (whence) {
141
+ case "start":
142
+ position = offset;
143
+ break;
144
+ case "current":
145
+ position += offset;
146
+ break;
147
+ case "end":
148
+ position = this.#writeIndex + offset;
149
+ break;
150
+ default:
151
+ throw new Error(`Invalid seek mode: ${whence}`);
152
+ }
153
+
154
+ if (position < 0 || position > this.#writeIndex) {
155
+ throw new Error("Seek out of bounds");
156
+ }
157
+
158
+ this.#readIndex = position;
159
+ return position;
160
+ }
161
+
127
162
  /**
128
163
  * Get the number of samples in a given duration.
129
164
  *
@@ -5,6 +5,7 @@ import { Client } from "../lib/client";
5
5
  import { CARTESIA_VERSION, constructApiUrl } from "../lib/constants";
6
6
  import type {
7
7
  ConnectionEventData,
8
+ ContinueRequest,
8
9
  EmitteryCallbacks,
9
10
  StreamOptions,
10
11
  StreamRequest,
@@ -45,7 +46,7 @@ export default class WebSocket extends Client {
45
46
  /**
46
47
  * Send a message over the WebSocket to start a stream.
47
48
  *
48
- * @param inputs - Stream options. Defined in the StreamRequest type.
49
+ * @param inputs - Generation parameters. Defined in the StreamRequest type.
49
50
  * @param options - Options for the stream.
50
51
  * @param options.timeout - The maximum time to wait for a chunk before cancelling the stream.
51
52
  * If set to `0`, the stream will not time out.
@@ -53,7 +54,7 @@ export default class WebSocket extends Client {
53
54
  * @returns An Emittery instance that emits messages from the WebSocket.
54
55
  * @returns An abort function that can be called to cancel the stream.
55
56
  */
56
- send({ ...inputs }: StreamRequest, { timeout = 0 }: StreamOptions = {}) {
57
+ send(inputs: StreamRequest, { timeout = 0 }: StreamOptions = {}) {
57
58
  if (!this.#isConnected) {
58
59
  throw new Error("Not connected to WebSocket. Call .connect() first.");
59
60
  }
@@ -151,6 +152,36 @@ export default class WebSocket extends Client {
151
152
  };
152
153
  }
153
154
 
155
+ /**
156
+ * Continue a stream.
157
+ *
158
+ * @param inputs - Generation parameters. Defined in the StreamRequest type, but must include a `context_id` field. `continue` is set to true by default.
159
+ */
160
+ continue(inputs: ContinueRequest) {
161
+ if (!this.#isConnected) {
162
+ throw new Error("Not connected to WebSocket. Call .connect() first.");
163
+ }
164
+
165
+ if (!inputs.context_id) {
166
+ throw new Error("context_id is required to continue a context.");
167
+ }
168
+ if (!inputs.output_format) {
169
+ inputs.output_format = {
170
+ container: this.#container,
171
+ encoding: this.#encoding,
172
+ sample_rate: this.#sampleRate,
173
+ };
174
+ }
175
+
176
+ // Send continue request.
177
+ this.socket?.send(
178
+ JSON.stringify({
179
+ continue: true,
180
+ ...inputs,
181
+ }),
182
+ );
183
+ }
184
+
154
185
  /**
155
186
  * Generate a unique ID suitable for a streaming context.
156
187
  *
@@ -52,10 +52,29 @@ export type StreamRequest = {
52
52
  context_id?: string;
53
53
  continue?: boolean;
54
54
  duration?: number;
55
- language?: string;
55
+ language?: Language;
56
56
  add_timestamps?: boolean;
57
57
  };
58
58
 
59
+ export type BytesRequest = Omit<
60
+ StreamRequest,
61
+ "continue" | "add_timestamps" | "context_id"
62
+ >;
63
+
64
+ export type ContinueRequest = StreamRequest & {
65
+ context_id: string;
66
+ };
67
+
68
+ export type Language =
69
+ | "en"
70
+ | "es"
71
+ | "fr"
72
+ | "de"
73
+ | "ja"
74
+ | "zh"
75
+ | "pt"
76
+ | (string & {});
77
+
59
78
  export type StreamOptions = {
60
79
  timeout?: number;
61
80
  };
@@ -112,6 +131,37 @@ export type CloneOptions =
112
131
  enhance?: boolean;
113
132
  };
114
133
 
134
+ export type VoiceChangerOptions = {
135
+ clip: File;
136
+ voice: { id: string }; // match VoiceSpecifier shape, but only id is supported for now
137
+ output_format:
138
+ | {
139
+ container: "mp3";
140
+ bit_rate: number;
141
+ sample_rate: number;
142
+ }
143
+ | {
144
+ container: "wav";
145
+ encoding: Encoding;
146
+ sample_rate: number;
147
+ bit_rate: number;
148
+ }
149
+ | {
150
+ container: "raw";
151
+ encoding: Encoding;
152
+ sample_rate: number;
153
+ };
154
+ };
155
+
156
+ export type LocalizeOptions = {
157
+ mode: "embedding";
158
+ embedding: number[];
159
+ } & {
160
+ language: Language;
161
+ dialect: string & {};
162
+ original_speaker_gender: "male" | "female" | (string & {});
163
+ };
164
+
115
165
  export interface VoiceToMix {
116
166
  id?: string;
117
167
  embedding?: number[];
@@ -130,7 +180,7 @@ export type Voice = {
130
180
  is_public: boolean;
131
181
  user_id: string;
132
182
  created_at: string;
133
- language: string;
183
+ language: Language;
134
184
  };
135
185
 
136
186
  export type CreateVoice = Pick<Voice, "name" | "description" | "embedding"> &
@@ -144,6 +194,14 @@ export type CloneResponse = {
144
194
  embedding: number[];
145
195
  };
146
196
 
197
+ export type VoiceChangerBytesResponse = {
198
+ buffer: ArrayBuffer;
199
+ };
200
+
201
+ export type LocalizeResponse = {
202
+ embedding: number[];
203
+ };
204
+
147
205
  export type MixVoicesResponse = {
148
206
  embedding: number[];
149
207
  };
@@ -0,0 +1,37 @@
1
+ import { Client } from "../lib/client";
2
+ import type { VoiceChangerBytesResponse, VoiceChangerOptions } from "../types";
3
+
4
+ export default class VoiceChanger extends Client {
5
+ async bytes(
6
+ options: VoiceChangerOptions,
7
+ ): Promise<VoiceChangerBytesResponse> {
8
+ const formData = new FormData();
9
+ formData.append("clip", options.clip); // TODO: handle Blobs that are not Files
10
+ formData.append("voice[id]", options.voice.id);
11
+
12
+ const fmt = options.output_format;
13
+ formData.append("output_format[container]", fmt.container);
14
+ if ("encoding" in fmt) {
15
+ formData.append("output_format[encoding]", fmt.encoding);
16
+ }
17
+ if ("bit_rate" in fmt) {
18
+ formData.append("output_format[bit_rate]", fmt.bit_rate.toString());
19
+ }
20
+ if ("sample_rate" in fmt) {
21
+ formData.append("output_format[sample_rate]", fmt.sample_rate.toString());
22
+ }
23
+
24
+ const response = await this._fetch("/voice-changer/bytes", {
25
+ method: "POST",
26
+ body: formData,
27
+ });
28
+
29
+ if (!response.ok) {
30
+ throw new Error(
31
+ `Voice changer error! status: ${response.status}, message: ${await response.text()}`,
32
+ );
33
+ }
34
+
35
+ return { buffer: await response.arrayBuffer() };
36
+ }
37
+ }
@@ -3,6 +3,8 @@ import type {
3
3
  CloneOptions,
4
4
  CloneResponse,
5
5
  CreateVoice,
6
+ LocalizeOptions,
7
+ LocalizeResponse,
6
8
  MixVoicesOptions,
7
9
  MixVoicesResponse,
8
10
  UpdateVoice,
@@ -55,13 +57,20 @@ export default class Voices extends Client {
55
57
  }
56
58
 
57
59
  async mix(options: MixVoicesOptions): Promise<MixVoicesResponse> {
58
- const request: MixVoicesOptions = options;
59
-
60
60
  const response = await this._fetch("/voices/mix", {
61
61
  method: "POST",
62
- body: JSON.stringify(request),
62
+ body: JSON.stringify(options),
63
63
  });
64
64
 
65
65
  return response.json() as Promise<MixVoicesResponse>;
66
66
  }
67
+
68
+ async localize(options: LocalizeOptions): Promise<LocalizeResponse> {
69
+ const response = await this._fetch("/voices/localize", {
70
+ method: "POST",
71
+ body: JSON.stringify(options),
72
+ });
73
+
74
+ return response.json() as Promise<LocalizeResponse>;
75
+ }
67
76
  }