sonilo 0.16.5 → 0.18.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 +59 -0
- package/dist/index.cjs +37 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -3
- package/dist/index.d.ts +108 -3
- package/dist/index.js +37 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -379,6 +379,15 @@ boolean (default off, free) ducks the background music/effects bed under the
|
|
|
379
379
|
dubbed voice while it speaks; when off the bed is kept at a constant level.
|
|
380
380
|
Every endpoint's `ducking` is default-off, so this one is no exception.
|
|
381
381
|
|
|
382
|
+
The optional `lipsync` boolean is the one parameter here that defaults **on**:
|
|
383
|
+
the speaker's mouth is re-rendered to match the dubbed speech. Pass
|
|
384
|
+
`lipsync: false` to leave the picture completely untouched instead — the video
|
|
385
|
+
comes back at its original resolution and frame rate rather than re-rendered,
|
|
386
|
+
and only the audio is replaced, so the mouths keep moving to the original
|
|
387
|
+
language. Reach for it on footage with no on-camera speaker, or when
|
|
388
|
+
preserving the exact original picture matters more than matching lip movement.
|
|
389
|
+
The background bed is rebuilt either way, so `ducking` behaves the same.
|
|
390
|
+
|
|
382
391
|
Dubbing is async-only, and the source video may be at most 300 seconds long.
|
|
383
392
|
You are billed per language. Dubbing has **no free trial allowance** — unlike
|
|
384
393
|
every other endpoint, every call bills from the first one (see
|
|
@@ -388,6 +397,56 @@ The result is a `DubbingResult`, whose `outputs` is a map of language code to
|
|
|
388
397
|
dubbed `.mp4` URL — not the `audio`/`video`/`output_url` shape the other
|
|
389
398
|
endpoints use.
|
|
390
399
|
|
|
400
|
+
### Subtitle scripts
|
|
401
|
+
|
|
402
|
+
`subtitles` supplies the lines to speak, one script per target language:
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
const result = await client.dubbing.generate(
|
|
406
|
+
{
|
|
407
|
+
videoUrl: "https://example.com/clip.mp4",
|
|
408
|
+
languages: ["ja", "es"],
|
|
409
|
+
subtitles: { ja: "./ja.srt", es: "https://example.com/es.vtt" },
|
|
410
|
+
exportSrt: true,
|
|
411
|
+
},
|
|
412
|
+
{ timeout: 7_200_000 },
|
|
413
|
+
);
|
|
414
|
+
console.log(result.subtitles?.ja); // re-timed .srt URL
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Rules:
|
|
418
|
+
|
|
419
|
+
- These are **target-language** scripts, not source-language transcripts.
|
|
420
|
+
- The keys must match `languages` exactly — a missing or extra code is a 422,
|
|
421
|
+
checked server-side before anything is charged.
|
|
422
|
+
- A value starting with `https://` is a URL; any other string is a local file
|
|
423
|
+
path (Node.js only). A browser can pass a `File` instead. The filename must
|
|
424
|
+
end in `.srt` or `.vtt`, and an uploaded script is at most 1 MiB.
|
|
425
|
+
- `exportSrt` requires `subtitles`. With it, each language's delivered audio is
|
|
426
|
+
force-aligned against its script and a re-timed SRT is returned under
|
|
427
|
+
`subtitles`, keeping your lines verbatim.
|
|
428
|
+
|
|
429
|
+
`subtitle_preflight` (what the pipeline made of each script) and
|
|
430
|
+
`subtitle_export` (how each re-timed SRT came out) are maps keyed by language.
|
|
431
|
+
Their numeric fields — `cue_count`, `changes_count`, `alignment_loss` — are
|
|
432
|
+
typed `number | string`, because a finished task carries them as strings; read
|
|
433
|
+
them through `Number(...)`. `report_url` is `string | null` — the key is always
|
|
434
|
+
written, so test the value, not the key. An export whose `status` is `blocked`
|
|
435
|
+
does not fail the task: the dubbed videos are still delivered and `subtitles`
|
|
436
|
+
simply lacks that language.
|
|
437
|
+
|
|
438
|
+
`submit()` returns a `DubbingTask`: the shared `SfxTask` plus the
|
|
439
|
+
acknowledgement's own `subtitle_preflight`. A `review_required` status there
|
|
440
|
+
means the pipeline **changed lines in the script you submitted**, and
|
|
441
|
+
`changes_count` says how many — worth checking without waiting for the dub:
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
const task = await client.dubbing.submit({ videoUrl, languages, subtitles });
|
|
445
|
+
for (const [language, report] of Object.entries(task.subtitle_preflight ?? {})) {
|
|
446
|
+
if (report.status !== "ok") console.warn(language, report.status, report.issues);
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
391
450
|
## Video analysis
|
|
392
451
|
|
|
393
452
|
`client.videoAnalysis` analyzes a video and returns a **creative brief** for
|
package/dist/index.cjs
CHANGED
|
@@ -756,6 +756,17 @@ var AudioDucking = class {
|
|
|
756
756
|
};
|
|
757
757
|
|
|
758
758
|
// src/resources/dubbing.ts
|
|
759
|
+
var SUBTITLE_EXTENSIONS = [".srt", ".vtt"];
|
|
760
|
+
async function toSubtitleBlob(language, value) {
|
|
761
|
+
const name = typeof value === "string" ? value : value?.name ?? "";
|
|
762
|
+
const lowered = name.toLowerCase();
|
|
763
|
+
if (!SUBTITLE_EXTENSIONS.some((ext) => lowered.endsWith(ext))) {
|
|
764
|
+
throw new SoniloError(
|
|
765
|
+
`subtitles[${language}]: "${name}" must be an .srt or .vtt file, or an https:// URL`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
return toUploadBlob(value);
|
|
769
|
+
}
|
|
759
770
|
async function buildDubbingForm(params) {
|
|
760
771
|
if (params.video === void 0 === (params.videoUrl === void 0)) {
|
|
761
772
|
throw new SoniloError("Provide exactly one of video or videoUrl");
|
|
@@ -779,12 +790,37 @@ async function buildDubbingForm(params) {
|
|
|
779
790
|
if (params.ducking !== void 0) {
|
|
780
791
|
form.set("ducking", String(params.ducking));
|
|
781
792
|
}
|
|
793
|
+
if (params.lipsync !== void 0) {
|
|
794
|
+
form.set("lipsync", String(params.lipsync));
|
|
795
|
+
}
|
|
796
|
+
if (params.subtitles !== void 0) {
|
|
797
|
+
for (const [language, value] of Object.entries(params.subtitles)) {
|
|
798
|
+
const field = `subtitles[${language}]`;
|
|
799
|
+
if (typeof value === "string" && value.toLowerCase().startsWith("https://")) {
|
|
800
|
+
form.set(field, value);
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
const { blob, filename } = await toSubtitleBlob(language, value);
|
|
804
|
+
form.set(field, blob, filename);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (params.exportSrt !== void 0) {
|
|
808
|
+
if (params.exportSrt && Object.keys(params.subtitles ?? {}).length === 0) {
|
|
809
|
+
throw new SoniloError("exportSrt requires subtitles \u2014 there is nothing to align against");
|
|
810
|
+
}
|
|
811
|
+
form.set("export_srt", String(params.exportSrt));
|
|
812
|
+
}
|
|
782
813
|
return form;
|
|
783
814
|
}
|
|
784
815
|
var Dubbing = class {
|
|
785
816
|
constructor(client) {
|
|
786
817
|
this.client = client;
|
|
787
818
|
}
|
|
819
|
+
/** The acknowledgement carries `subtitle_preflight` when scripts were sent,
|
|
820
|
+
* which is why this returns `DubbingTask` rather than the shared `SfxTask`:
|
|
821
|
+
* a `review_required` preflight means the pipeline altered lines in the
|
|
822
|
+
* script that was submitted, and typing it away hides that from every
|
|
823
|
+
* caller who only ever sees the 202. */
|
|
788
824
|
async submit(params) {
|
|
789
825
|
const res = await this.client.request("/v1/dubbing", {
|
|
790
826
|
method: "POST",
|
|
@@ -831,7 +867,7 @@ var VideoAnalysis = class {
|
|
|
831
867
|
};
|
|
832
868
|
|
|
833
869
|
// src/version.ts
|
|
834
|
-
var VERSION = "0.
|
|
870
|
+
var VERSION = "0.18.0";
|
|
835
871
|
|
|
836
872
|
// src/client.ts
|
|
837
873
|
var DEFAULT_BASE_URL = "https://api.sonilo.com";
|