Node.js Workflow

Upload individual recordings, attach them to a voice, then trigger a build.

Prerequisites

  • Node.js 18+
  • PRO plan (or higher) account and API token (RESEMBLE_API_KEY)
  • Folder with paired .wav files and transcripts (clip-01.wav, clip-01.txt, …)
$npm init -y
>npm install @resemble/node

1. Bootstrap the script

1// resemble-clone-voice-recording/index.js
2import * as Resemble from "@resemble/node";
3import fs from "fs";
4import path from "path";
5
6const apiKey = process.env.RESEMBLE_API_KEY;
7if (!apiKey) {
8 console.error("Set RESEMBLE_API_KEY before running.");
9 process.exit(1);
10}
11
12Resemble.Resemble.setApiKey(apiKey);
13
14const args = process.argv.slice(2);
15if (args.length !== 2) {
16 console.error("Usage: node index.js <voice_name> <recordings_folder>");
17 process.exit(1);
18}
19
20const [voiceName, recordingsFolder] = args;

2. Create the voice

1async function createVoice(name) {
2 console.log(`Creating voice ${name}...`);
3 const response = await Resemble.Resemble.v2.voices.create({ name });
4
5 if (!response.success) {
6 throw new Error(JSON.stringify(response));
7 }
8
9 const voice = response.item;
10 console.log(`Voice UUID: ${voice.uuid} (status: ${voice.status})`);
11 return voice.uuid;
12}

3. Read recordings from disk

1function readFolder(folderPath) {
2 const entries = [];
3
4 fs.readdirSync(folderPath).forEach((filename) => {
5 if (!filename.endsWith(".wav")) return;
6
7 const transcriptFile = filename.replace(".wav", ".txt");
8 const transcriptPath = path.join(folderPath, transcriptFile);
9
10 if (!fs.existsSync(transcriptPath)) {
11 console.warn(`Skipping ${filename}; missing transcript.`);
12 return;
13 }
14
15 entries.push({
16 filePath: path.join(folderPath, filename),
17 name: transcriptFile,
18 text: fs.readFileSync(transcriptPath, "utf-8"),
19 });
20 });
21
22 return entries;
23}

Target at least 20 clean samples; recordings longer than 12 seconds are ignored during training.

4. Upload recordings

1async function uploadRecordings(voiceUuid, folderPath) {
2 const recordings = readFolder(folderPath);
3 let success = 0;
4
5 for (const recording of recordings) {
6 console.log(`Uploading ${recording.name}...`);
7 const file = fs.createReadStream(recording.filePath);
8 const size = fs.statSync(recording.filePath).size;
9
10 const response = await Resemble.Resemble.v2.recordings.create(
11 voiceUuid,
12 {
13 emotion: "neutral",
14 is_active: true,
15 name: recording.name,
16 text: recording.text,
17 },
18 file,
19 size,
20 );
21
22 if (response.success) {
23 success += 1;
24 } else {
25 console.error(`Failed to upload ${recording.name}`, response);
26 }
27 }
28
29 console.log(`Uploaded ${success}/${recordings.length} recordings.`);
30}

5. Trigger the build

1async function triggerVoiceBuild(voiceUuid) {
2 const response = await Resemble.Resemble.v2.voices.build(voiceUuid);
3 if (!response.success) {
4 throw new Error(`Failed to start build: ${JSON.stringify(response)}`);
5 }
6
7 console.log("Build request accepted. Monitor status in the dashboard or via the API.");
8}

6. Run everything

1async function main() {
2 const voiceUuid = await createVoice(voiceName);
3 await uploadRecordings(voiceUuid, recordingsFolder);
4 await triggerVoiceBuild(voiceUuid);
5}
6
7main().catch((err) => {
8 console.error(err);
9 process.exit(1);
10});
$RESEMBLE_API_KEY=... node index.js "My Voice" ./example-data

The script prints upload status and kicks off training. Check progress with List Voices or the Resemble dashboard.