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 +78 -1
- package/dist/index.cjs +72 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -2
- package/dist/index.d.ts +113 -2
- package/dist/index.js +72 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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.
|