sonilo 0.13.0 → 0.15.0

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
@@ -248,6 +248,42 @@ Input videos may be at most 180 seconds long.
248
248
  Use `submit()` instead of `generate()` to get a `task_id` back immediately and
249
249
  poll it yourself with `client.tasks.wait<SoundResult>(taskId)`.
250
250
 
251
+ ## Audio ducking
252
+
253
+ `client.audioDucking.submit()` / `.generate()` mix an existing music bed under
254
+ an existing voice track, dipping the music wherever the voice speaks and
255
+ lifting it back in the gaps. Nothing is generated — both inputs are yours.
256
+ Reach for it when the music is fixed or external; when the music is being
257
+ generated for the same clip anyway, `videoToSound` or `videoToMusic` with
258
+ `ducking: true` duck internally as part of that one call instead.
259
+
260
+ ```ts
261
+ import { SoniloClient, download } from "sonilo";
262
+ import { writeFile } from "node:fs/promises";
263
+
264
+ const client = new SoniloClient();
265
+
266
+ const result = await client.audioDucking.generate({
267
+ voice: "./interview.mp4", // Node path; File/Blob in the browser, or `voiceUrl`
268
+ musicUrl: "https://example.com/bed.wav",
269
+ });
270
+
271
+ await writeFile(
272
+ result.output_type === "video" ? "ducked.mp4" : "ducked.wav",
273
+ await download(result.output_url!),
274
+ );
275
+ ```
276
+
277
+ Params: exactly one of `voice` / `voiceUrl` and exactly one of `music` /
278
+ `musicUrl` (a local input and a URL mix freely across the two). The **voice**
279
+ may be audio or video — a video's own audio track becomes the voice, and the
280
+ ducked mix is muxed back into a new video, so the result is a `.mp4` instead
281
+ of a `.wav`. The **music** must be audio: the API never probes it for a video
282
+ stream, so a video there is silently mishandled rather than rejected. Each
283
+ input is capped at 360 seconds. Async-only; the result is a `DuckingResult`
284
+ carrying the same flat `output_url` / `output_type` envelope as
285
+ `videoToSound`, with no stems.
286
+
251
287
  ## Dubbing
252
288
 
253
289
  `client.dubbing.submit()` / `.generate()` dub a video into one or more target
@@ -302,6 +338,47 @@ The result is a `DubbingResult`, whose `outputs` is a map of language code to
302
338
  dubbed `.mp4` URL — not the `audio`/`video`/`output_url` shape the other
303
339
  endpoints use.
304
340
 
341
+ ## Video analysis
342
+
343
+ `client.videoAnalysis` analyzes a video and returns a **creative brief** for
344
+ scoring it. Nothing is generated: no audio, no video, no file to download.
345
+ The result is the work order — a time-aligned `segments` plan plus one
346
+ `prompt` per requested variation, each ready to hand straight to
347
+ `videoToMusic`, `videoToSfx`, `videoToSound` or their video-to-video
348
+ counterparts.
349
+
350
+ Pass exactly one of `video` / `videoUrl`, plus optional `prompt` (guidance
351
+ for the analysis, at most 2000 characters) and `variantsNum` (1-5, default
352
+ 1 — billed per brief). Source videos may be at most 600 seconds long, and
353
+ billing has a 10-second floor, so a very short clip still costs the same as a
354
+ 10-second one.
355
+
356
+ ```ts
357
+ const brief = await client.videoAnalysis.analyze({
358
+ video: "trailer.mp4",
359
+ prompt: "focus on the chase",
360
+ variantsNum: 2,
361
+ });
362
+
363
+ for (const segment of brief.segments ?? []) {
364
+ console.log(`${segment.start}-${segment.end}s [${segment.label}] ${segment.prompt}`);
365
+ }
366
+
367
+ // Feed a variation's prompt straight into a generation call.
368
+ const task = await client.videoToMusic.submit({
369
+ video: "trailer.mp4",
370
+ prompt: brief.variations![0]!.prompt,
371
+ });
372
+ ```
373
+
374
+ The method is `analyze`, not `generate`, for the same reason there is no
375
+ download helper on the result: every other resource returns something you
376
+ save, and this one never does. Both `segments` and `variations` are optional
377
+ on the type because a `processing` or `failed` poll carries neither. Use
378
+ `submit()` instead of `analyze()` to get a `task_id` back immediately and
379
+ poll it yourself with
380
+ `client.tasks.wait<VideoAnalysisResult>(taskId)`.
381
+
305
382
  ## Configuration
306
383
 
307
384
  ```ts
@@ -390,7 +467,7 @@ endpoints — no card required:
390
467
 
391
468
  | Free runs | Endpoints |
392
469
  | --- | --- |
393
- | 2 each | text-to-music, text-to-sfx, audio-ducking |
470
+ | 2 each | text-to-music, text-to-sfx, audio-ducking, video-analysis |
394
471
  | 1 each | video-to-music, video-to-sfx, video-to-video-music, video-to-video-sfx, video-to-sound, video-to-video-sound |
395
472
  | 0 | dubbing |
396
473
 
package/dist/index.cjs CHANGED
@@ -712,6 +712,43 @@ var VideoToVideoSound = class {
712
712
  }
713
713
  };
714
714
 
715
+ // src/resources/audioDucking.ts
716
+ var AudioDucking = class {
717
+ constructor(client) {
718
+ this.client = client;
719
+ }
720
+ async submit(params) {
721
+ if (params.voice === void 0 === (params.voiceUrl === void 0)) {
722
+ throw new SoniloError("Provide exactly one of voice or voiceUrl");
723
+ }
724
+ if (params.music === void 0 === (params.musicUrl === void 0)) {
725
+ throw new SoniloError("Provide exactly one of music or musicUrl");
726
+ }
727
+ const form = new FormData();
728
+ if (params.voice !== void 0) {
729
+ const { blob, filename } = await toUploadBlob(params.voice);
730
+ form.set("voice_file", blob, filename);
731
+ } else {
732
+ form.set("voice_url", params.voiceUrl);
733
+ }
734
+ if (params.music !== void 0) {
735
+ const { blob, filename } = await toUploadBlob(params.music);
736
+ form.set("music_file", blob, filename);
737
+ } else {
738
+ form.set("music_url", params.musicUrl);
739
+ }
740
+ const res = await this.client.request("/v1/audio-ducking", {
741
+ method: "POST",
742
+ body: form
743
+ });
744
+ return await res.json();
745
+ }
746
+ async generate(params, opts) {
747
+ const task = await this.submit(params);
748
+ return this.client.tasks.wait(task.task_id, opts);
749
+ }
750
+ };
751
+
715
752
  // src/resources/dubbing.ts
716
753
  async function buildDubbingForm(params) {
717
754
  if (params.video === void 0 === (params.videoUrl === void 0)) {
@@ -755,8 +792,40 @@ var Dubbing = class {
755
792
  }
756
793
  };
757
794
 
795
+ // src/resources/videoAnalysis.ts
796
+ var VideoAnalysis = class {
797
+ constructor(client) {
798
+ this.client = client;
799
+ }
800
+ async submit(params) {
801
+ if (params.video === void 0 === (params.videoUrl === void 0)) {
802
+ throw new SoniloError("Provide exactly one of video or videoUrl");
803
+ }
804
+ const form = new FormData();
805
+ if (params.video !== void 0) {
806
+ const { blob, filename } = await toUploadBlob(params.video);
807
+ form.set("video", blob, filename);
808
+ } else {
809
+ form.set("video_url", params.videoUrl);
810
+ }
811
+ if (params.prompt !== void 0) form.set("prompt", params.prompt);
812
+ if (params.variantsNum !== void 0) {
813
+ form.set("variants_num", String(params.variantsNum));
814
+ }
815
+ const res = await this.client.request("/v1/video-analysis", {
816
+ method: "POST",
817
+ body: form
818
+ });
819
+ return await res.json();
820
+ }
821
+ async analyze(params, opts) {
822
+ const task = await this.submit(params);
823
+ return this.client.tasks.wait(task.task_id, opts);
824
+ }
825
+ };
826
+
758
827
  // src/version.ts
759
- var VERSION = "0.13.0";
828
+ var VERSION = "0.15.0";
760
829
 
761
830
  // src/client.ts
762
831
  var DEFAULT_BASE_URL = "https://api.sonilo.com";
@@ -787,7 +856,9 @@ var SoniloClient = class {
787
856
  this.videoToVideoSfx = new VideoToVideoSfx(this);
788
857
  this.videoToSound = new VideoToSound(this);
789
858
  this.videoToVideoSound = new VideoToVideoSound(this);
859
+ this.audioDucking = new AudioDucking(this);
790
860
  this.dubbing = new Dubbing(this);
861
+ this.videoAnalysis = new VideoAnalysis(this);
791
862
  }
792
863
  /**
793
864
  * Perform an authenticated request; throws a typed error on non-2xx.