sonilo 0.15.0 → 0.16.1

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/README.md CHANGED
@@ -127,6 +127,53 @@ if (result.ducked) {
127
127
  }
128
128
  ```
129
129
 
130
+ ### Stems (async)
131
+
132
+ Set `stems: true` to also split the generated track into four separated
133
+ stems — `drums`, `bass`, `vocals`, `other`. It is **free of charge**, and
134
+ available on `textToMusic` and `videoToMusic`. It requires the async task API
135
+ (`mode: "async"` — the backend rejects it on the plain stream with a 400), so
136
+ it is only meaningful via `submit()`; on `videoToMusic` it splits the
137
+ **generated** music, never the source video's own audio.
138
+
139
+ When separation succeeds, the result gains a `stems` array with one entry per
140
+ stream: `{ stream_index, drums, bass, vocals, other }`, each stem an ordinary
141
+ media object (`url`, `content_type`, `file_size`) you can pass to
142
+ `download()`. **Look entries up by `stream_index`, never by array position** —
143
+ `stems` carries only the streams that separated successfully, so it can be
144
+ shorter than `audio`.
145
+
146
+ Failures land in `stems_error`, a string present when separation failed wholly
147
+ or in part, or was skipped. It can appear **alongside a partial `stems`
148
+ array**, so never treat it as "no stems" — check `stems` itself for what did
149
+ arrive. Either way the generated `audio` is unaffected.
150
+
151
+ Separation runs after generation and typically adds 2-6 minutes to the wait
152
+ (it gives up after 30), so raise `tasks.wait`'s `timeout` beyond the 10-minute
153
+ default. The stems normally follow the request's `outputFormat`; each stem's
154
+ own `content_type` reports what was actually delivered.
155
+
156
+ ```ts
157
+ const task = await client.textToMusic.submit({
158
+ prompt: "warm lo-fi piano",
159
+ duration: 30,
160
+ stems: true, // free — adds drums/bass/vocals/other alongside the mix
161
+ });
162
+ const result = await client.tasks.wait<MusicTaskResult>(task.task_id, {
163
+ timeout: 2_400_000, // separation can add up to 30 minutes
164
+ });
165
+
166
+ if (result.stems_error) console.warn(result.stems_error); // may be partial
167
+ for (const track of result.audio ?? []) {
168
+ const split = result.stems?.find((s) => s.stream_index === track.stream_index);
169
+ if (!split) continue; // this stream did not separate — see stems_error
170
+ await writeFile("drums.m4a", await download(split.drums));
171
+ await writeFile("bass.m4a", await download(split.bass));
172
+ await writeFile("vocals.m4a", await download(split.vocals));
173
+ await writeFile("other.m4a", await download(split.other));
174
+ }
175
+ ```
176
+
130
177
  ### Variants (async)
131
178
 
132
179
  `variantsNum` generates several distinct music variants in one request (1-10,
@@ -329,7 +376,7 @@ boolean (default off, free) ducks the background music/effects bed under the
329
376
  dubbed voice while it speaks; when off the bed is kept at a constant level.
330
377
  Every endpoint's `ducking` is default-off, so this one is no exception.
331
378
 
332
- Dubbing is async-only, and the source video may be at most 180 seconds long.
379
+ Dubbing is async-only, and the source video may be at most 300 seconds long.
333
380
  You are billed per language. Dubbing has **no free trial allowance** — unlike
334
381
  every other endpoint, every call bills from the first one (see
335
382
  [Free trial](#free-trial)).
package/dist/index.cjs CHANGED
@@ -347,8 +347,8 @@ var TextToMusic = class {
347
347
  /**
348
348
  * Submit an async text-to-music task; poll with
349
349
  * `client.tasks.wait<MusicTaskResult>(task.task_id)`. Required for
350
- * a non-m4a `outputFormat` and `variantsNum` above 1. `stream()`/`generate()`
351
- * remain the streaming path.
350
+ * a non-m4a `outputFormat`, `variantsNum` above 1, and `stems`.
351
+ * `stream()`/`generate()` remain the streaming path.
352
352
  */
353
353
  async submit(params) {
354
354
  const mode = params.mode ?? "async";
@@ -368,6 +368,9 @@ var TextToMusic = class {
368
368
  if (params.variantsNum !== void 0) {
369
369
  form.set("variants_num", String(params.variantsNum));
370
370
  }
371
+ if (params.stems !== void 0) {
372
+ form.set("stems", String(params.stems));
373
+ }
371
374
  const res = await this.client.request("/v1/text-to-music", {
372
375
  method: "POST",
373
376
  body: form
@@ -451,9 +454,9 @@ var VideoToMusic = class {
451
454
  /**
452
455
  * Submit an async video-to-music task; poll its result with
453
456
  * `client.tasks.wait<MusicTaskResult>(task.task_id)`. Required for
454
- * `isolateVocals`/`preserveSpeech`, a non-m4a `outputFormat`, and
455
- * `variantsNum` above 1 — the backend rejects all of these on the plain
456
- * stream, and they only ever run in async mode.
457
+ * `isolateVocals`/`preserveSpeech`, a non-m4a `outputFormat`,
458
+ * `variantsNum` above 1, and `stems` — the backend rejects all of these on
459
+ * the plain stream, and they only ever run in async mode.
457
460
  */
458
461
  async submit(params) {
459
462
  if (params.video === void 0 === (params.videoUrl === void 0)) {
@@ -463,11 +466,11 @@ var VideoToMusic = class {
463
466
  const needsAsync = params.isolateVocals || params.preserveSpeech || params.ducking !== void 0 || // Any non-m4a container is a finalize-time transcode, so it needs
464
467
  // async. Checking != "m4a" rather than == "wav" keeps this correct
465
468
  // as formats are added (mp3 landed after the original check).
466
- params.outputFormat !== void 0 && params.outputFormat !== "m4a" || params.variantsNum !== void 0 && params.variantsNum > 1;
469
+ params.outputFormat !== void 0 && params.outputFormat !== "m4a" || params.variantsNum !== void 0 && params.variantsNum > 1 || params.stems !== void 0;
467
470
  if (mode === void 0) mode = "async";
468
471
  if (needsAsync && mode !== "async") {
469
472
  throw new SoniloError(
470
- 'isolateVocals/preserveSpeech/ducking/outputFormat other than "m4a"/variantsNum > 1 require mode: "async"'
473
+ 'isolateVocals/preserveSpeech/ducking/stems/outputFormat other than "m4a"/variantsNum > 1 require mode: "async"'
471
474
  );
472
475
  }
473
476
  const form = new FormData();
@@ -497,6 +500,9 @@ var VideoToMusic = class {
497
500
  if (params.variantsNum !== void 0) {
498
501
  form.set("variants_num", String(params.variantsNum));
499
502
  }
503
+ if (params.stems !== void 0) {
504
+ form.set("stems", String(params.stems));
505
+ }
500
506
  if (params.promptInfluence !== void 0) {
501
507
  form.set("prompt_influence", String(params.promptInfluence));
502
508
  }
@@ -825,7 +831,7 @@ var VideoAnalysis = class {
825
831
  };
826
832
 
827
833
  // src/version.ts
828
- var VERSION = "0.15.0";
834
+ var VERSION = "0.16.1";
829
835
 
830
836
  // src/client.ts
831
837
  var DEFAULT_BASE_URL = "https://api.sonilo.com";