nixamp 0.28.43 → 0.28.45
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 +8 -3
- package/dist/accounts.js +9 -1
- package/dist/live-interpreter.d.ts +1 -72
- package/dist/live-interpreter.js +1 -269
- package/dist/live-recovery.d.ts +1 -6
- package/dist/live-recovery.js +1 -11
- package/dist/live-voice.d.ts +6 -76
- package/dist/live-voice.js +6 -352
- package/dist/owner.js +4 -0
- package/dist/paywall.js +1 -1
- package/dist/pcm-wav.d.ts +1 -2
- package/dist/pcm-wav.js +1 -27
- package/dist/server.d.ts +16 -0
- package/dist/server.js +77 -6
- package/dist/shared-translation.d.ts +6 -53
- package/dist/shared-translation.js +7 -336
- package/dist/speaker-turns.d.ts +1 -32
- package/dist/speaker-turns.js +1 -30
- package/dist/speech.d.ts +3 -5
- package/dist/speech.js +4 -7
- package/dist/tickets.js +1 -1
- package/dist/translation-passes.d.ts +5 -96
- package/dist/translation-passes.js +5 -236
- package/dist/upgrade-allowance.d.ts +1 -23
- package/dist/upgrade-allowance.js +1 -73
- package/dist/voice-profile.d.ts +1 -7
- package/dist/voice-profile.js +1 -53
- package/dist/voice-provider.d.ts +1 -9
- package/dist/voice-provider.js +1 -27
- package/package.json +4 -2
- package/src/accounts.ts +9 -1
- package/src/live-interpreter.ts +1 -213
- package/src/live-recovery.ts +1 -10
- package/src/live-voice.ts +6 -304
- package/src/owner.ts +3 -0
- package/src/paywall.ts +1 -1
- package/src/pcm-wav.ts +1 -26
- package/src/server.ts +57 -6
- package/src/shared-translation.ts +7 -222
- package/src/speaker-turns.ts +1 -34
- package/src/speech.ts +4 -5
- package/src/tickets.ts +1 -1
- package/src/translation-passes.ts +6 -239
- package/src/types/auth-system.d.ts +12 -3
- package/src/upgrade-allowance.ts +1 -72
- package/src/voice-profile.ts +1 -46
- package/src/voice-provider.ts +1 -22
- package/web/dist/.well-known/openwebring.json +6 -0
- package/web/dist/assets/{ar-CR2NEW77.js → ar-8fDonUoB.js} +1 -1
- package/web/dist/assets/{cs-ivXHTYBK.js → cs-DCClolTy.js} +1 -1
- package/web/dist/assets/{da-DQEGAEdk.js → da-C3RPhXUd.js} +1 -1
- package/web/dist/assets/{de-B5VaSv3O.js → de-CYBoEomk.js} +1 -1
- package/web/dist/assets/{es-DUTtfGnk.js → es-DS3ls4xT.js} +1 -1
- package/web/dist/assets/{fi-CzQZtpO5.js → fi-DbCZpO89.js} +1 -1
- package/web/dist/assets/{fr-C2g4DUBr.js → fr-D0z3UpdL.js} +1 -1
- package/web/dist/assets/{hi-DKB7ZsZ8.js → hi-BTU3sRlQ.js} +1 -1
- package/web/dist/assets/{hls-3VKVEQE3-Bc75Z5iH.js → hls-3VKVEQE3-CQXTVHn_.js} +1 -1
- package/web/dist/assets/{hu--oH7WvAR.js → hu-B37Q8ldT.js} +1 -1
- package/web/dist/assets/{id-CjlpBIJc.js → id-4pdXNa4G.js} +1 -1
- package/web/dist/assets/index-BDBbN5Qy.js +22 -0
- package/web/dist/assets/{it-Dbctup0F.js → it-CTe69-UY.js} +1 -1
- package/web/dist/assets/{mpegts-D9VECdKk.js → mpegts-BBmkDppm.js} +1 -1
- package/web/dist/assets/{mpegts-LO6RVLD6-CSb7Fz4U.js → mpegts-LO6RVLD6-CUVdjYFi.js} +1 -1
- package/web/dist/assets/{nl-BYw8ZbV_.js → nl-CxrmHrMn.js} +1 -1
- package/web/dist/assets/{ru-DFGYCrj-.js → ru-zjVCfIKq.js} +1 -1
- package/web/dist/assets/{sv-DbO1Sc1v.js → sv-CEoiT2Lr.js} +1 -1
- package/web/dist/assets/{uk-BTXHwsjV.js → uk-ZC6JzQvD.js} +1 -1
- package/web/dist/assets/{vi-DJ-vIPLS.js → vi-D9anF-cl.js} +1 -1
- package/web/dist/assets/{zh-D3Gju_Y2.js → zh-BeLYdC0I.js} +1 -1
- package/web/dist/index.html +6 -5
- package/web/dist/sw.js +23 -22
- package/web/dist/assets/index-CF04ablL.js +0 -3
package/README.md
CHANGED
|
@@ -619,9 +619,14 @@ multiplied by five. Credentials and balances never travel in checkout URLs.
|
|
|
619
619
|
New checkout creation is capped at five per account and fifty per account server
|
|
620
620
|
per UTC day, plus IP and request throttles; retries reuse the original invoice.
|
|
621
621
|
|
|
622
|
-
After the ten free sessions, new sessions require purchased credit. Configure `
|
|
623
|
-
|
|
624
|
-
|
|
622
|
+
After the ten free sessions, new sessions require purchased credit. Configure `COINPAY_API_KEY`
|
|
623
|
+
(the older name `COINPAY_X402_KEY` still works; `COINPAY_API_KEY` wins when both are set)
|
|
624
|
+
with `payments:create` and `payments:read` permission and at least one business wallet. The scoped
|
|
625
|
+
key supplies the merchant identity, so it must belong to nixamp's own CoinPay business. Set
|
|
626
|
+
`COINPAY_WEBHOOK_SECRET` to that business's webhook secret and its webhook URL to
|
|
627
|
+
`https://<site>/api/v1/webhooks/coinpay`, and a paid order is credited even if the buyer never
|
|
628
|
+
comes back to the page. The webhook body is only a nudge: the order is re-checked with CoinPay
|
|
629
|
+
before anything is credited, and it is credited once however many times it is delivered. Free sessions and existing credit still work during a
|
|
625
630
|
checkout outage. A self-hosted operator explicitly sponsoring API usage may set
|
|
626
631
|
`NIXAMP_TRANSLATION_BILLING=off`.
|
|
627
632
|
|
package/dist/accounts.js
CHANGED
|
@@ -96,11 +96,19 @@ export class Accounts {
|
|
|
96
96
|
(options.system
|
|
97
97
|
? null
|
|
98
98
|
: new PostgresAdapter({ connectionString: options.connectionString }));
|
|
99
|
+
// Without a secret the module signs with a public default, and anyone can
|
|
100
|
+
// mint a session for any account. Refuse rather than run that way.
|
|
101
|
+
if (!options.system && !options.secret) {
|
|
102
|
+
throw new Error("NIXAMP_JWT_SECRET is required for accounts");
|
|
103
|
+
}
|
|
99
104
|
this.system =
|
|
100
105
|
options.system ??
|
|
101
106
|
createAuthSystem({
|
|
102
107
|
adapter,
|
|
103
|
-
|
|
108
|
+
// tokenOptions.secret, not jwtSecret: the module has no top-level
|
|
109
|
+
// jwtSecret, and until 2026-10-09 this passed one, so every JWT was
|
|
110
|
+
// signed with the module's public default secret.
|
|
111
|
+
tokenOptions: { secret: options.secret },
|
|
104
112
|
// The module defaults to requiring an uppercase and a lowercase
|
|
105
113
|
// letter. Its rules have to match the ones checked above, or a password
|
|
106
114
|
// this accepts is refused a layer down in a different sentence.
|
|
@@ -1,72 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export interface Caption {
|
|
3
|
-
channel: string;
|
|
4
|
-
at: number;
|
|
5
|
-
until: number;
|
|
6
|
-
text: string;
|
|
7
|
-
language?: string;
|
|
8
|
-
original?: string;
|
|
9
|
-
sourceLanguage?: string;
|
|
10
|
-
speaker?: string;
|
|
11
|
-
voiceProfile?: "lower" | "higher" | "unknown";
|
|
12
|
-
}
|
|
13
|
-
export interface VoiceChoice {
|
|
14
|
-
id: string;
|
|
15
|
-
name: string;
|
|
16
|
-
gender: string;
|
|
17
|
-
language: string;
|
|
18
|
-
}
|
|
19
|
-
export interface AudioWindow {
|
|
20
|
-
samples: Float32Array;
|
|
21
|
-
at: number;
|
|
22
|
-
until: number;
|
|
23
|
-
freshAt: number;
|
|
24
|
-
}
|
|
25
|
-
export interface Speaker {
|
|
26
|
-
id: string;
|
|
27
|
-
profile: string;
|
|
28
|
-
voice: string;
|
|
29
|
-
}
|
|
30
|
-
/** Match provider labels across overlapping timestamps. A speaker absent from
|
|
31
|
-
* the rolling context gets a new label; pitch alone never identifies someone. */
|
|
32
|
-
export declare class SpeakerTracker {
|
|
33
|
-
private readonly random;
|
|
34
|
-
private previous;
|
|
35
|
-
private sequence;
|
|
36
|
-
readonly speakers: Map<string, Speaker>;
|
|
37
|
-
constructor(random?: () => number);
|
|
38
|
-
reset(): void;
|
|
39
|
-
reconcile(turns: SpeakerTurn[], at: number, voices: VoiceChoice[]): Map<string, Speaker>;
|
|
40
|
-
}
|
|
41
|
-
/** Listener-local interpretation for the playing media. One request in flight and only the latest pending audio window. */
|
|
42
|
-
export declare class Interpreter {
|
|
43
|
-
private readonly options;
|
|
44
|
-
readonly tracker: SpeakerTracker;
|
|
45
|
-
private generation;
|
|
46
|
-
private pending;
|
|
47
|
-
private committedUntil;
|
|
48
|
-
private running;
|
|
49
|
-
private controller;
|
|
50
|
-
private translating;
|
|
51
|
-
private translationController;
|
|
52
|
-
private recognitionFailures;
|
|
53
|
-
private translationFailures;
|
|
54
|
-
private translations;
|
|
55
|
-
constructor(options: {
|
|
56
|
-
language: () => string;
|
|
57
|
-
speakers: () => boolean;
|
|
58
|
-
voices: () => VoiceChoice[];
|
|
59
|
-
channel: () => string;
|
|
60
|
-
lines: (lines: Caption[]) => void;
|
|
61
|
-
status: (text: string) => void;
|
|
62
|
-
failed: (message: string) => void;
|
|
63
|
-
fetcher?: typeof fetch;
|
|
64
|
-
});
|
|
65
|
-
reset(): void;
|
|
66
|
-
push(window: AudioWindow): void;
|
|
67
|
-
private json;
|
|
68
|
-
private run;
|
|
69
|
-
private translate;
|
|
70
|
-
}
|
|
71
|
-
/** Preserve all words while respecting the speech API's character limit. */
|
|
72
|
-
export declare function splitCaption(line: Caption): Caption[];
|
|
1
|
+
export { Interpreter, SpeakerTracker, splitCaption, type Caption, type VoiceChoice, type AudioWindow, type Speaker } from "@profullstack/live-translate/core";
|
package/dist/live-interpreter.js
CHANGED
|
@@ -1,269 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { encodeWav } from "./pcm-wav.js";
|
|
3
|
-
/** Match provider labels across overlapping timestamps. A speaker absent from
|
|
4
|
-
* the rolling context gets a new label; pitch alone never identifies someone. */
|
|
5
|
-
export class SpeakerTracker {
|
|
6
|
-
random;
|
|
7
|
-
previous = [];
|
|
8
|
-
sequence = 0;
|
|
9
|
-
speakers = new Map();
|
|
10
|
-
constructor(random = Math.random) {
|
|
11
|
-
this.random = random;
|
|
12
|
-
}
|
|
13
|
-
reset() { this.previous = []; this.sequence = 0; this.speakers.clear(); }
|
|
14
|
-
reconcile(turns, at, voices) {
|
|
15
|
-
const links = new Map();
|
|
16
|
-
const used = new Set();
|
|
17
|
-
const candidates = [];
|
|
18
|
-
const totals = new Map();
|
|
19
|
-
for (const turn of turns)
|
|
20
|
-
for (const old of this.previous) {
|
|
21
|
-
const overlap = Math.min(at + turn.end * 1000, old.end) - Math.max(at + turn.start * 1000, old.start);
|
|
22
|
-
if (overlap > 120) {
|
|
23
|
-
const key = `${turn.speaker}|${old.speaker}`;
|
|
24
|
-
totals.set(key, (totals.get(key) ?? 0) + overlap);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
for (const [key, overlap] of totals) {
|
|
28
|
-
const [local, global] = key.split("|");
|
|
29
|
-
candidates.push({ local: local, global: global, overlap });
|
|
30
|
-
}
|
|
31
|
-
for (const one of candidates.sort((a, b) => b.overlap - a.overlap)) {
|
|
32
|
-
const speaker = this.speakers.get(one.global);
|
|
33
|
-
if (speaker && !links.has(one.local) && !used.has(one.global)) {
|
|
34
|
-
links.set(one.local, speaker);
|
|
35
|
-
used.add(one.global);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
for (const turn of turns) {
|
|
39
|
-
let speaker = links.get(turn.speaker);
|
|
40
|
-
if (!speaker) {
|
|
41
|
-
// Assign contrasting stock voices, without guessing a person's
|
|
42
|
-
// gender from pitch or a noisy, short opening phrase.
|
|
43
|
-
const taken = new Set([...this.speakers.values()].map(one => one.voice));
|
|
44
|
-
const available = voices.filter(voice => !taken.has(voice.id));
|
|
45
|
-
const pool = available.length ? available : voices;
|
|
46
|
-
const voice = pool[Math.floor(this.random() * pool.length)];
|
|
47
|
-
speaker = { id: `speaker-${++this.sequence}`, profile: turn.profile, voice: voice?.id ?? "auto" };
|
|
48
|
-
this.speakers.set(speaker.id, speaker);
|
|
49
|
-
links.set(turn.speaker, speaker);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
this.previous = turns.map(turn => ({ start: at + turn.start * 1000, end: at + turn.end * 1000, speaker: links.get(turn.speaker).id }));
|
|
53
|
-
const present = new Set(this.previous.map(turn => turn.speaker));
|
|
54
|
-
for (const key of this.speakers.keys())
|
|
55
|
-
if (this.speakers.size > 64 && !present.has(key))
|
|
56
|
-
this.speakers.delete(key);
|
|
57
|
-
return links;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/** Listener-local interpretation for the playing media. One request in flight and only the latest pending audio window. */
|
|
61
|
-
export class Interpreter {
|
|
62
|
-
options;
|
|
63
|
-
tracker = new SpeakerTracker();
|
|
64
|
-
generation = 0;
|
|
65
|
-
pending = null;
|
|
66
|
-
committedUntil = null;
|
|
67
|
-
running = null;
|
|
68
|
-
controller = null;
|
|
69
|
-
translating = null;
|
|
70
|
-
translationController = null;
|
|
71
|
-
recognitionFailures = 0;
|
|
72
|
-
translationFailures = 0;
|
|
73
|
-
translations = [];
|
|
74
|
-
constructor(options) {
|
|
75
|
-
this.options = options;
|
|
76
|
-
}
|
|
77
|
-
reset() {
|
|
78
|
-
this.generation++;
|
|
79
|
-
this.controller?.abort();
|
|
80
|
-
this.translationController?.abort();
|
|
81
|
-
this.pending = null;
|
|
82
|
-
this.running = null;
|
|
83
|
-
this.translating = null;
|
|
84
|
-
this.recognitionFailures = this.translationFailures = 0;
|
|
85
|
-
this.translations = [];
|
|
86
|
-
this.committedUntil = null;
|
|
87
|
-
this.tracker.reset();
|
|
88
|
-
}
|
|
89
|
-
push(window) {
|
|
90
|
-
this.committedUntil ??= window.freshAt;
|
|
91
|
-
this.pending = window;
|
|
92
|
-
if (this.running === null)
|
|
93
|
-
void this.run(this.generation);
|
|
94
|
-
}
|
|
95
|
-
async json(path, init, signal) {
|
|
96
|
-
const response = await (this.options.fetcher ?? fetch)(path, { ...init, signal });
|
|
97
|
-
const body = await response.json();
|
|
98
|
-
if (!response.ok)
|
|
99
|
-
throw audioError(body.error || "Audio translation is unavailable.", response.status);
|
|
100
|
-
return body;
|
|
101
|
-
}
|
|
102
|
-
async run(generation) {
|
|
103
|
-
this.running = generation;
|
|
104
|
-
try {
|
|
105
|
-
while (this.pending && generation === this.generation) {
|
|
106
|
-
const window = this.pending;
|
|
107
|
-
this.pending = null;
|
|
108
|
-
try {
|
|
109
|
-
if (Date.now() - window.until > 12_000)
|
|
110
|
-
continue;
|
|
111
|
-
const controller = new AbortController();
|
|
112
|
-
this.controller = controller;
|
|
113
|
-
const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]);
|
|
114
|
-
const speakers = this.options.speakers(), target = this.options.language();
|
|
115
|
-
const heard = await this.json(speakers ? "/api/v1/speech/speakers" : "/api/v1/speech/transcribe?live=1", {
|
|
116
|
-
method: "POST", headers: { "content-type": "audio/wav" }, body: new Uint8Array(encodeWav(speakers ? window.samples : window.samples.slice(-80_000))),
|
|
117
|
-
}, signal);
|
|
118
|
-
if (generation !== this.generation)
|
|
119
|
-
return;
|
|
120
|
-
this.recognitionFailures = 0;
|
|
121
|
-
const language = heard.language;
|
|
122
|
-
let lines = [];
|
|
123
|
-
if (speakers) {
|
|
124
|
-
const links = this.tracker.reconcile(heard.turns ?? [], window.at, this.options.voices());
|
|
125
|
-
// The watermark follows emitted words, not the newest capture
|
|
126
|
-
// interval: an overlapping window can recover audio that arrived
|
|
127
|
-
// while recognition/translation was busy. Keep unfinished phrases
|
|
128
|
-
// in that overlap so short capture intervals do not split every
|
|
129
|
-
// sentence (especially damaging when German changes word order).
|
|
130
|
-
const cutoff = this.committedUntil ?? window.freshAt;
|
|
131
|
-
const turns = heard.turns ?? [];
|
|
132
|
-
for (const [index, turn] of turns.entries()) {
|
|
133
|
-
const speaker = links.get(turn.speaker);
|
|
134
|
-
const words = turn.words.filter(word => window.at + (word.start + word.end) * 500 >= cutoff &&
|
|
135
|
-
(window.at + word.end * 1000 <= window.until - 100 || /[.!?。!?]$/.test(word.text)));
|
|
136
|
-
let phrase = [];
|
|
137
|
-
const emit = () => {
|
|
138
|
-
if (!phrase.length)
|
|
139
|
-
return;
|
|
140
|
-
lines.push({ channel: this.options.channel(), at: window.at + phrase[0].start * 1000,
|
|
141
|
-
until: window.at + phrase.at(-1).end * 1000, text: phrase.map(word => word.text).join(" ").trim(),
|
|
142
|
-
language, speaker: speaker.id, voiceProfile: turn.profile });
|
|
143
|
-
phrase = [];
|
|
144
|
-
};
|
|
145
|
-
for (const word of words) {
|
|
146
|
-
if (phrase.length && word.start - phrase.at(-1).end >= 0.45)
|
|
147
|
-
emit();
|
|
148
|
-
phrase.push(word);
|
|
149
|
-
if (/[.!?。!?]$/.test(word.text) || phrase.map(word => word.text).join(" ").length >= 400)
|
|
150
|
-
emit();
|
|
151
|
-
}
|
|
152
|
-
if (phrase.length && (index < turns.length - 1 ||
|
|
153
|
-
window.until - (window.at + phrase.at(-1).end * 1000) >= 350 ||
|
|
154
|
-
window.until - (window.at + phrase[0].start * 1000) >= 3000))
|
|
155
|
-
emit();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
else if (heard.text) {
|
|
159
|
-
lines = [{ channel: this.options.channel(), at: window.freshAt, until: window.until, text: heard.text, language }];
|
|
160
|
-
}
|
|
161
|
-
if (!lines.length)
|
|
162
|
-
continue;
|
|
163
|
-
this.committedUntil = Math.max(this.committedUntil ?? window.freshAt, ...lines.map(line => line.until));
|
|
164
|
-
// Drop expired work instead of turning off an otherwise healthy stream.
|
|
165
|
-
this.translations = this.translations.filter(item => Date.now() - item.until <= 12_000).slice(-11);
|
|
166
|
-
this.translations.push({ lines, language, target, until: window.until });
|
|
167
|
-
// Recognition of the next audio window can proceed while the text
|
|
168
|
-
// model translates this phrase. Voice synthesis is a third stage.
|
|
169
|
-
if (this.translating === null)
|
|
170
|
-
void this.translate(this.generation);
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
if (generation !== this.generation)
|
|
174
|
-
return;
|
|
175
|
-
if (!transientAudioError(error) || ++this.recognitionFailures >= 3)
|
|
176
|
-
throw error;
|
|
177
|
-
this.options.status("Speech recognition interrupted · listening for the next phrase…");
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
catch (error) {
|
|
182
|
-
if (generation === this.generation) {
|
|
183
|
-
const message = error instanceof Error ? error.message : "Live translation stopped.";
|
|
184
|
-
this.reset();
|
|
185
|
-
this.options.failed(message);
|
|
186
|
-
this.options.status(message);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
finally {
|
|
190
|
-
if (this.running === generation)
|
|
191
|
-
this.running = null;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
async translate(generation) {
|
|
195
|
-
this.translating = generation;
|
|
196
|
-
try {
|
|
197
|
-
while (this.translations.length && generation === this.generation) {
|
|
198
|
-
const item = this.translations.shift();
|
|
199
|
-
try {
|
|
200
|
-
let { lines } = item;
|
|
201
|
-
const { language, target, until } = item;
|
|
202
|
-
if (Date.now() - until > 12_000)
|
|
203
|
-
continue;
|
|
204
|
-
const controller = new AbortController();
|
|
205
|
-
this.translationController = controller;
|
|
206
|
-
if (target && language !== target) {
|
|
207
|
-
if (!language)
|
|
208
|
-
throw audioError("The audio language could not be detected. Waiting for clearer speech.", 502);
|
|
209
|
-
const translated = await this.json("/api/v1/translate?live=1", {
|
|
210
|
-
method: "POST", headers: { "content-type": "application/json" },
|
|
211
|
-
body: JSON.stringify({ texts: lines.map(line => line.text), from: language, to: target }),
|
|
212
|
-
}, AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]));
|
|
213
|
-
if (translated.texts.length !== lines.length || translated.texts.some(text => !text.trim()))
|
|
214
|
-
throw audioError("Translation omitted a phrase. Waiting for the next phrase.", 502);
|
|
215
|
-
lines = lines.map((line, i) => ({ ...line, original: line.text, sourceLanguage: language, language: target, text: translated.texts[i] }));
|
|
216
|
-
}
|
|
217
|
-
if (generation !== this.generation)
|
|
218
|
-
return;
|
|
219
|
-
if (Date.now() - until > 12_000)
|
|
220
|
-
continue;
|
|
221
|
-
this.translationFailures = 0;
|
|
222
|
-
this.options.lines(lines.flatMap(line => splitCaption(line)));
|
|
223
|
-
this.options.status(target ? `${language} → ${target} · live translation` : `Original audio language: ${language || "detecting…"}`);
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
if (generation !== this.generation)
|
|
227
|
-
return;
|
|
228
|
-
if (!transientAudioError(error) || ++this.translationFailures >= 3)
|
|
229
|
-
throw error;
|
|
230
|
-
this.options.status("Translation interrupted · catching up with live speech…");
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
if (generation === this.generation) {
|
|
236
|
-
const message = error instanceof Error ? error.message : "Live translation stopped.";
|
|
237
|
-
this.reset();
|
|
238
|
-
this.options.failed(message);
|
|
239
|
-
this.options.status(message);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
finally {
|
|
243
|
-
if (this.translating === generation)
|
|
244
|
-
this.translating = null;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
/** Preserve all words while respecting the speech API's character limit. */
|
|
249
|
-
export function splitCaption(line) {
|
|
250
|
-
const text = line.text.trim();
|
|
251
|
-
if (!text)
|
|
252
|
-
return [];
|
|
253
|
-
const parts = [];
|
|
254
|
-
let rest = text;
|
|
255
|
-
while (rest.length > 600) {
|
|
256
|
-
const space = rest.lastIndexOf(" ", 600);
|
|
257
|
-
const end = space > 300 ? space : 600;
|
|
258
|
-
parts.push(rest.slice(0, end));
|
|
259
|
-
rest = rest.slice(end).trimStart();
|
|
260
|
-
}
|
|
261
|
-
if (rest)
|
|
262
|
-
parts.push(rest);
|
|
263
|
-
let offset = 0;
|
|
264
|
-
return parts.map(part => {
|
|
265
|
-
const at = line.at + (line.until - line.at) * offset / text.length;
|
|
266
|
-
offset += part.length + 1;
|
|
267
|
-
return { ...line, text: part, at, until: Math.min(line.until, line.at + (line.until - line.at) * offset / text.length) };
|
|
268
|
-
});
|
|
269
|
-
}
|
|
1
|
+
export { Interpreter, SpeakerTracker, splitCaption } from "@profullstack/live-translate/core";
|
package/dist/live-recovery.d.ts
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* and transport failures may recover, with a bounded consecutive failure count. */
|
|
3
|
-
export declare function transientAudioError(error: unknown): boolean;
|
|
4
|
-
export declare function audioError(message: string, status: number): Error & {
|
|
5
|
-
status: number;
|
|
6
|
-
};
|
|
1
|
+
export { audioError, transientAudioError } from "@profullstack/live-translate/core";
|
package/dist/live-recovery.js
CHANGED
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* and transport failures may recover, with a bounded consecutive failure count. */
|
|
3
|
-
export function transientAudioError(error) {
|
|
4
|
-
const status = error?.status;
|
|
5
|
-
if (typeof status === "number")
|
|
6
|
-
return [408, 502, 503, 504].includes(status);
|
|
7
|
-
return error instanceof TypeError || (error instanceof Error && ["AbortError", "TimeoutError"].includes(error.name));
|
|
8
|
-
}
|
|
9
|
-
export function audioError(message, status) {
|
|
10
|
-
return Object.assign(new Error(message), { status });
|
|
11
|
-
}
|
|
1
|
+
export { audioError, transientAudioError } from "@profullstack/live-translate/core";
|
package/dist/live-voice.d.ts
CHANGED
|
@@ -1,77 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
export declare
|
|
6
|
-
|
|
7
|
-
export declare const LIVE_VOICE_LANGUAGES: Set<string>;
|
|
8
|
-
export interface LiveVoiceChoice {
|
|
9
|
-
id: string;
|
|
10
|
-
name: string;
|
|
11
|
-
gender: string;
|
|
12
|
-
language: string;
|
|
13
|
-
}
|
|
14
|
-
export interface VoiceRequest {
|
|
15
|
-
text: string;
|
|
16
|
-
language: string;
|
|
17
|
-
voice?: string;
|
|
18
|
-
profile?: VoiceProfile;
|
|
19
|
-
channel?: string;
|
|
20
|
-
speaker?: string;
|
|
21
|
-
}
|
|
22
|
-
export declare class LiveVoice {
|
|
23
|
-
private readonly key;
|
|
24
|
-
private readonly fetcher;
|
|
25
|
-
private readonly now;
|
|
26
|
-
private catalog;
|
|
27
|
-
private catalogUntil;
|
|
28
|
-
private readonly cache;
|
|
29
|
-
private readonly pending;
|
|
30
|
-
private readonly usage;
|
|
31
|
-
private readonly charsPerMinute;
|
|
32
|
-
private readonly requests;
|
|
33
|
-
private readonly grants;
|
|
34
|
-
private readonly activeBy;
|
|
35
|
-
private readonly db?;
|
|
36
|
-
private readonly billing?;
|
|
37
|
-
private schema;
|
|
38
|
-
private readonly dailyChars;
|
|
39
|
-
private cleanupAt;
|
|
40
|
-
private readonly hearing;
|
|
41
|
-
private readonly dailyAudioSeconds;
|
|
42
|
-
private readonly userDailyChars;
|
|
43
|
-
private readonly userDailyAudioSeconds;
|
|
44
|
-
private providerPause;
|
|
45
|
-
private checkProvider;
|
|
46
|
-
private providerFailed;
|
|
47
|
-
constructor(options?: {
|
|
48
|
-
apiKey?: string;
|
|
49
|
-
fetcher?: typeof fetch;
|
|
50
|
-
now?: () => number;
|
|
51
|
-
charsPerMinute?: number;
|
|
52
|
-
dailyChars?: number;
|
|
53
|
-
dailyAudioSeconds?: number;
|
|
54
|
-
userDailyChars?: number;
|
|
55
|
-
userDailyAudioSeconds?: number;
|
|
56
|
-
db?: Queryable;
|
|
57
|
-
billing?: TranslationMeter;
|
|
58
|
-
});
|
|
59
|
-
available(): boolean;
|
|
60
|
-
/** Optional diarization, billed only while a signed-in listener requests it.
|
|
61
|
-
* Rolling audio is bounded to 15 seconds, including overlap. Every second
|
|
62
|
-
* submitted (also repeated context) consumes the persistent provider budget. */
|
|
63
|
-
hear(bytes: Uint8Array, by: string, signal?: AbortSignal, meter?: TranslationMeter | undefined, resource?: string): Promise<SpeakerTranscript>;
|
|
64
|
-
voices(): Promise<LiveVoiceChoice[]>;
|
|
65
|
-
/** A 90-second capability for one channel, never the viewer's account credential. */
|
|
66
|
-
grant(by: string, channel: string): Promise<{
|
|
67
|
-
token: string;
|
|
68
|
-
expires: number;
|
|
69
|
-
}>;
|
|
70
|
-
authorize(token: string, channel: string, chars: number): Promise<string>;
|
|
71
|
-
private ensure;
|
|
72
|
-
private reserve;
|
|
73
|
-
private charge;
|
|
74
|
-
private checkRequest;
|
|
75
|
-
/** Stream the first request immediately; concurrent listeners share its cached result. */
|
|
76
|
-
stream(ask: VoiceRequest, by: string, signal?: AbortSignal, meter?: TranslationMeter | undefined): Promise<Response>;
|
|
1
|
+
/** ElevenLabs Flash/Scribe for live captions, from @profullstack/live-translate.
|
|
2
|
+
* nixamp keeps its `nxd_` grant tokens: captions.ts and older pages expect them. */
|
|
3
|
+
import { LiveVoice as Base } from "@profullstack/live-translate";
|
|
4
|
+
export { LIVE_VOICE_MODEL, LIVE_VOICE_RATE, LIVE_VOICE_LANGUAGES, type LiveVoiceChoice, type VoiceRequest } from "@profullstack/live-translate";
|
|
5
|
+
export declare class LiveVoice extends Base {
|
|
6
|
+
constructor(options?: ConstructorParameters<typeof Base>[0]);
|
|
77
7
|
}
|