nixamp 0.27.2 → 0.28.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 +39 -16
- package/backtoschool/dist/assets/ar-DK3LI2hP.js +1 -0
- package/backtoschool/dist/assets/cs-C9cckHFh.js +1 -0
- package/backtoschool/dist/assets/da-BpNIZAZo.js +1 -0
- package/backtoschool/dist/assets/de-C1DX_kDk.js +1 -0
- package/backtoschool/dist/assets/es-bymdOEnB.js +1 -0
- package/backtoschool/dist/assets/fi-p1DAJXf3.js +1 -0
- package/backtoschool/dist/assets/fr-w1qH9bbD.js +1 -0
- package/backtoschool/dist/assets/hi-DnZJakpY.js +1 -0
- package/backtoschool/dist/assets/hu-N7uSyLAk.js +1 -0
- package/backtoschool/dist/assets/id-CX3MVU3E.js +1 -0
- package/backtoschool/dist/assets/index-4LdMSur6.js +93 -0
- package/backtoschool/dist/assets/{index-OE2xWZUH.css → index-D06NfY1L.css} +1 -1
- package/backtoschool/dist/assets/it-CQ0H28FI.js +1 -0
- package/backtoschool/dist/assets/nl-DJqCLHMq.js +1 -0
- package/backtoschool/dist/assets/ru-Rk2yagke.js +1 -0
- package/backtoschool/dist/assets/sv-1r9eOl4-.js +1 -0
- package/backtoschool/dist/assets/uk-CrRsLNHV.js +1 -0
- package/backtoschool/dist/assets/vi-B5kxrjP7.js +1 -0
- package/backtoschool/dist/assets/zh-BB_S8QpG.js +1 -0
- package/backtoschool/dist/index.html +50 -38
- package/dist/admin.js +5 -4
- package/dist/event-writer.d.ts +39 -0
- package/dist/event-writer.js +139 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.js +81 -0
- package/dist/live-api.d.ts +2 -0
- package/dist/live-api.js +37 -2
- package/dist/live-interpreter.d.ts +3 -1
- package/dist/live-interpreter.js +109 -83
- package/dist/live-recovery.d.ts +6 -0
- package/dist/live-recovery.js +11 -0
- package/dist/locales/ar.d.ts +201 -0
- package/dist/locales/ar.js +200 -0
- package/dist/locales/cs.d.ts +201 -0
- package/dist/locales/cs.js +200 -0
- package/dist/locales/da.d.ts +201 -0
- package/dist/locales/da.js +200 -0
- package/dist/locales/de.d.ts +201 -0
- package/dist/locales/de.js +200 -0
- package/dist/locales/en.d.ts +201 -0
- package/dist/locales/en.js +201 -0
- package/dist/locales/es.d.ts +201 -0
- package/dist/locales/es.js +200 -0
- package/dist/locales/fi.d.ts +201 -0
- package/dist/locales/fi.js +200 -0
- package/dist/locales/fr.d.ts +201 -0
- package/dist/locales/fr.js +200 -0
- package/dist/locales/hi.d.ts +201 -0
- package/dist/locales/hi.js +200 -0
- package/dist/locales/hu.d.ts +201 -0
- package/dist/locales/hu.js +200 -0
- package/dist/locales/id.d.ts +201 -0
- package/dist/locales/id.js +200 -0
- package/dist/locales/it.d.ts +201 -0
- package/dist/locales/it.js +200 -0
- package/dist/locales/nl.d.ts +201 -0
- package/dist/locales/nl.js +200 -0
- package/dist/locales/ru.d.ts +201 -0
- package/dist/locales/ru.js +200 -0
- package/dist/locales/sv.d.ts +201 -0
- package/dist/locales/sv.js +200 -0
- package/dist/locales/uk.d.ts +201 -0
- package/dist/locales/uk.js +200 -0
- package/dist/locales/vi.d.ts +201 -0
- package/dist/locales/vi.js +200 -0
- package/dist/locales/zh.d.ts +201 -0
- package/dist/locales/zh.js +200 -0
- package/dist/mail-sender.d.ts +9 -0
- package/dist/mail-sender.js +11 -0
- package/dist/main.js +26 -12
- package/dist/notify.d.ts +3 -3
- package/dist/notify.js +8 -5
- package/dist/password-reset.d.ts +2 -3
- package/dist/password-reset.js +4 -2
- package/dist/server.d.ts +2 -0
- package/dist/server.js +13 -1
- package/dist/shared-translation.d.ts +1 -0
- package/dist/shared-translation.js +32 -23
- package/dist/ui-aliases.d.ts +4 -0
- package/dist/ui-aliases.js +41 -0
- package/dist/ui-languages.d.ts +62 -0
- package/dist/ui-languages.js +32 -0
- package/package.json +1 -1
- package/src/admin.ts +5 -4
- package/src/event-writer.ts +126 -0
- package/src/i18n.ts +74 -0
- package/src/live-api.ts +26 -2
- package/src/live-interpreter.ts +87 -68
- package/src/live-recovery.ts +10 -0
- package/src/locales/ar.ts +201 -0
- package/src/locales/cs.ts +201 -0
- package/src/locales/da.ts +201 -0
- package/src/locales/de.ts +201 -0
- package/src/locales/en.ts +201 -0
- package/src/locales/es.ts +201 -0
- package/src/locales/fi.ts +201 -0
- package/src/locales/fr.ts +201 -0
- package/src/locales/hi.ts +201 -0
- package/src/locales/hu.ts +201 -0
- package/src/locales/id.ts +201 -0
- package/src/locales/it.ts +201 -0
- package/src/locales/nl.ts +201 -0
- package/src/locales/ru.ts +201 -0
- package/src/locales/sv.ts +201 -0
- package/src/locales/uk.ts +201 -0
- package/src/locales/vi.ts +201 -0
- package/src/locales/zh.ts +201 -0
- package/src/mail-sender.ts +12 -0
- package/src/main.ts +25 -12
- package/src/notify.ts +10 -7
- package/src/password-reset.ts +5 -5
- package/src/server.ts +14 -1
- package/src/shared-translation.ts +27 -19
- package/src/ui-aliases.ts +42 -0
- package/src/ui-languages.ts +31 -0
- package/web/dist/assets/ar-DK3LI2hP.js +1 -0
- package/web/dist/assets/background.worker-Hz7oaQ8X.js +547 -0
- package/web/dist/assets/cs-C9cckHFh.js +1 -0
- package/web/dist/assets/da-BpNIZAZo.js +1 -0
- package/web/dist/assets/de-C1DX_kDk.js +1 -0
- package/web/dist/assets/es-bymdOEnB.js +1 -0
- package/web/dist/assets/fi-p1DAJXf3.js +1 -0
- package/web/dist/assets/fr-w1qH9bbD.js +1 -0
- package/web/dist/assets/hi-DnZJakpY.js +1 -0
- package/web/dist/assets/{hls-3VKVEQE3-CAL4qBEq.js → hls-3VKVEQE3-DURNoQ5W.js} +1 -1
- package/web/dist/assets/hu-N7uSyLAk.js +1 -0
- package/web/dist/assets/id-CX3MVU3E.js +1 -0
- package/web/dist/assets/index-BXJlt2r6.js +3 -0
- package/web/dist/assets/index-Brm9OAsr.css +1 -0
- package/web/dist/assets/it-CQ0H28FI.js +1 -0
- package/web/dist/assets/{mpegts-H9oC2fAK.js → mpegts-BdyqUrVC.js} +1 -1
- package/web/dist/assets/{mpegts-LO6RVLD6-Bkd9Sqr6.js → mpegts-LO6RVLD6-yGogIzG9.js} +1 -1
- package/web/dist/assets/nl-DJqCLHMq.js +1 -0
- package/web/dist/assets/ru-Rk2yagke.js +1 -0
- package/web/dist/assets/sv-1r9eOl4-.js +1 -0
- package/web/dist/assets/uk-CrRsLNHV.js +1 -0
- package/web/dist/assets/vi-B5kxrjP7.js +1 -0
- package/web/dist/assets/zh-BB_S8QpG.js +1 -0
- package/web/dist/index.html +132 -131
- package/web/dist/sw.js +24 -7
- package/backtoschool/dist/assets/index-pQ_uH4bZ.js +0 -93
- package/web/dist/assets/background.worker-DH8ag5Q4.js +0 -547
- package/web/dist/assets/index-CLeEWlms.js +0 -3
- package/web/dist/assets/index-D34dTY18.css +0 -1
package/dist/live-api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventWriterError } from "./event-writer.js";
|
|
1
2
|
import { tokenFrom } from "./accounts.js";
|
|
2
3
|
import { INVITATION_STATES, LIVE_EVENT_KINDS, LIVE_EVENT_STATUSES, LiveEventError, isTicketed, } from "./live-events.js";
|
|
3
4
|
import { LAYOUT_SCOPES, LayoutError, PANEL_REGISTRY, layoutNameFor, presetLayout, } from "./layouts.js";
|
|
@@ -19,13 +20,13 @@ function json(response, code, body) {
|
|
|
19
20
|
});
|
|
20
21
|
response.end(value);
|
|
21
22
|
}
|
|
22
|
-
async function body(request) {
|
|
23
|
+
async function body(request, limit = 64 * 1024) {
|
|
23
24
|
const chunks = [];
|
|
24
25
|
let size = 0;
|
|
25
26
|
for await (const chunk of request) {
|
|
26
27
|
const buffer = chunk;
|
|
27
28
|
size += buffer.length;
|
|
28
|
-
if (size >
|
|
29
|
+
if (size > limit)
|
|
29
30
|
throw new LiveEventError("body too large", 413);
|
|
30
31
|
chunks.push(buffer);
|
|
31
32
|
}
|
|
@@ -187,6 +188,32 @@ export async function handleLiveApi(request, response, url, options) {
|
|
|
187
188
|
!path.startsWith("/api/v1/layout-presets"))
|
|
188
189
|
return false;
|
|
189
190
|
try {
|
|
191
|
+
if (path === "/api/v1/events/ai-draft") {
|
|
192
|
+
if (request.method !== "POST") {
|
|
193
|
+
json(response, 405, { error: "POST only" });
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
const account = await requiredAccount(request, response, options);
|
|
197
|
+
if (!account)
|
|
198
|
+
return true;
|
|
199
|
+
if (!options.eventWriter) {
|
|
200
|
+
json(response, 503, { error: "The AI writer is temporarily unavailable. You can keep writing here." });
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
const input = await body(request, 12 * 1024);
|
|
204
|
+
const controller = new AbortController();
|
|
205
|
+
const closed = () => controller.abort();
|
|
206
|
+
response.once("close", closed);
|
|
207
|
+
try {
|
|
208
|
+
const result = await options.eventWriter.draft(account.id, input, controller.signal);
|
|
209
|
+
if (!response.destroyed)
|
|
210
|
+
json(response, 200, result);
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
response.off("close", closed);
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
190
217
|
if (path === "/api/v1/events") {
|
|
191
218
|
if (request.method === "GET") {
|
|
192
219
|
const account = await signedIn(request, options.accounts);
|
|
@@ -559,6 +586,14 @@ export async function handleLiveApi(request, response, url, options) {
|
|
|
559
586
|
return true;
|
|
560
587
|
}
|
|
561
588
|
catch (error) {
|
|
589
|
+
if (error instanceof EventWriterError) {
|
|
590
|
+
if (!response.destroyed) {
|
|
591
|
+
if (error.retryAfter)
|
|
592
|
+
response.setHeader("retry-after", String(error.retryAfter));
|
|
593
|
+
json(response, error.status, { error: error.message });
|
|
594
|
+
}
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
562
597
|
if (error instanceof LiveEventError || error instanceof LayoutError || error instanceof RoomError) {
|
|
563
598
|
json(response, error.status, { error: error.message });
|
|
564
599
|
return true;
|
|
@@ -49,6 +49,8 @@ export declare class Interpreter {
|
|
|
49
49
|
private controller;
|
|
50
50
|
private translating;
|
|
51
51
|
private translationController;
|
|
52
|
+
private recognitionFailures;
|
|
53
|
+
private translationFailures;
|
|
52
54
|
private translations;
|
|
53
55
|
constructor(options: {
|
|
54
56
|
language: () => string;
|
|
@@ -57,7 +59,7 @@ export declare class Interpreter {
|
|
|
57
59
|
channel: () => string;
|
|
58
60
|
lines: (lines: Caption[]) => void;
|
|
59
61
|
status: (text: string) => void;
|
|
60
|
-
failed: () => void;
|
|
62
|
+
failed: (message: string) => void;
|
|
61
63
|
fetcher?: typeof fetch;
|
|
62
64
|
});
|
|
63
65
|
reset(): void;
|
package/dist/live-interpreter.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { audioError, transientAudioError } from "./live-recovery.js";
|
|
1
2
|
import { encodeWav } from "./pcm-wav.js";
|
|
2
3
|
/** Match provider labels across overlapping timestamps. A speaker absent from
|
|
3
4
|
* the rolling context gets a new label; pitch alone never identifies someone. */
|
|
@@ -67,6 +68,8 @@ export class Interpreter {
|
|
|
67
68
|
controller = null;
|
|
68
69
|
translating = null;
|
|
69
70
|
translationController = null;
|
|
71
|
+
recognitionFailures = 0;
|
|
72
|
+
translationFailures = 0;
|
|
70
73
|
translations = [];
|
|
71
74
|
constructor(options) {
|
|
72
75
|
this.options = options;
|
|
@@ -78,6 +81,7 @@ export class Interpreter {
|
|
|
78
81
|
this.pending = null;
|
|
79
82
|
this.running = null;
|
|
80
83
|
this.translating = null;
|
|
84
|
+
this.recognitionFailures = this.translationFailures = 0;
|
|
81
85
|
this.translations = [];
|
|
82
86
|
this.committedUntil = null;
|
|
83
87
|
this.tracker.reset();
|
|
@@ -92,7 +96,7 @@ export class Interpreter {
|
|
|
92
96
|
const response = await (this.options.fetcher ?? fetch)(path, { ...init, signal });
|
|
93
97
|
const body = await response.json();
|
|
94
98
|
if (!response.ok)
|
|
95
|
-
throw
|
|
99
|
+
throw audioError(body.error || "Audio translation is unavailable.", response.status);
|
|
96
100
|
return body;
|
|
97
101
|
}
|
|
98
102
|
async run(generation) {
|
|
@@ -101,74 +105,85 @@ export class Interpreter {
|
|
|
101
105
|
while (this.pending && generation === this.generation) {
|
|
102
106
|
const window = this.pending;
|
|
103
107
|
this.pending = null;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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))
|
|
144
155
|
emit();
|
|
145
156
|
}
|
|
146
|
-
if (phrase.length && (index < turns.length - 1 ||
|
|
147
|
-
window.until - (window.at + phrase.at(-1).end * 1000) >= 350 ||
|
|
148
|
-
window.until - (window.at + phrase[0].start * 1000) >= 3000))
|
|
149
|
-
emit();
|
|
150
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);
|
|
151
171
|
}
|
|
152
|
-
|
|
153
|
-
|
|
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…");
|
|
154
178
|
}
|
|
155
|
-
if (!lines.length)
|
|
156
|
-
continue;
|
|
157
|
-
this.committedUntil = Math.max(this.committedUntil ?? window.freshAt, ...lines.map(line => line.until));
|
|
158
|
-
if (this.translations.length >= 12)
|
|
159
|
-
throw new Error("Translation fell behind. Try again after the language model has warmed up.");
|
|
160
|
-
this.translations.push({ lines, language, target, until: window.until });
|
|
161
|
-
// Recognition of the next audio window can proceed while the text
|
|
162
|
-
// model translates this phrase. Voice synthesis is a third stage.
|
|
163
|
-
if (this.translating === null)
|
|
164
|
-
void this.translate(this.generation);
|
|
165
179
|
}
|
|
166
180
|
}
|
|
167
181
|
catch (error) {
|
|
168
182
|
if (generation === this.generation) {
|
|
183
|
+
const message = error instanceof Error ? error.message : "Live translation stopped.";
|
|
169
184
|
this.reset();
|
|
170
|
-
this.options.failed();
|
|
171
|
-
this.options.status(
|
|
185
|
+
this.options.failed(message);
|
|
186
|
+
this.options.status(message);
|
|
172
187
|
}
|
|
173
188
|
}
|
|
174
189
|
finally {
|
|
@@ -181,36 +196,47 @@ export class Interpreter {
|
|
|
181
196
|
try {
|
|
182
197
|
while (this.translations.length && generation === this.generation) {
|
|
183
198
|
const item = this.translations.shift();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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…");
|
|
200
231
|
}
|
|
201
|
-
if (generation !== this.generation)
|
|
202
|
-
return;
|
|
203
|
-
if (Date.now() - until > 12_000)
|
|
204
|
-
throw new Error("Translation fell behind. Try again after the language model has warmed up.");
|
|
205
|
-
this.options.lines(lines.flatMap(line => splitCaption(line)));
|
|
206
|
-
this.options.status(target ? `${language} → ${target} · live translation` : `Original audio language: ${language || "detecting…"}`);
|
|
207
232
|
}
|
|
208
233
|
}
|
|
209
234
|
catch (error) {
|
|
210
235
|
if (generation === this.generation) {
|
|
236
|
+
const message = error instanceof Error ? error.message : "Live translation stopped.";
|
|
211
237
|
this.reset();
|
|
212
|
-
this.options.failed();
|
|
213
|
-
this.options.status(
|
|
238
|
+
this.options.failed(message);
|
|
239
|
+
this.options.status(message);
|
|
214
240
|
}
|
|
215
241
|
}
|
|
216
242
|
finally {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Access, billing and throttling failures are terminal. Only transient service
|
|
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
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Access, billing and throttling failures are terminal. Only transient service
|
|
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
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
"Interface language": string;
|
|
3
|
+
Language: string;
|
|
4
|
+
"Translation language": string;
|
|
5
|
+
"Original (auto-detect)": string;
|
|
6
|
+
Play: string;
|
|
7
|
+
Pause: string;
|
|
8
|
+
Stop: string;
|
|
9
|
+
Next: string;
|
|
10
|
+
Previous: string;
|
|
11
|
+
Select: string;
|
|
12
|
+
"Next/Prev": string;
|
|
13
|
+
Quit: string;
|
|
14
|
+
"Now Playing": string;
|
|
15
|
+
"Spectrum Analyser": string;
|
|
16
|
+
Playlist: string;
|
|
17
|
+
Files: string;
|
|
18
|
+
"Open files": string;
|
|
19
|
+
"Open folder": string;
|
|
20
|
+
"Add to library": string;
|
|
21
|
+
"Replace playlist": string;
|
|
22
|
+
Filter: string;
|
|
23
|
+
Search: string;
|
|
24
|
+
"Nothing loaded.": string;
|
|
25
|
+
"Empty.": string;
|
|
26
|
+
"Loading\u2026": string;
|
|
27
|
+
"Connecting\u2026": string;
|
|
28
|
+
Connected: string;
|
|
29
|
+
"Not connected": string;
|
|
30
|
+
"No source": string;
|
|
31
|
+
Playing: string;
|
|
32
|
+
Stopped: string;
|
|
33
|
+
Volume: string;
|
|
34
|
+
Seek: string;
|
|
35
|
+
"Full screen": string;
|
|
36
|
+
Download: string;
|
|
37
|
+
Share: string;
|
|
38
|
+
Copy: string;
|
|
39
|
+
"Copy link": string;
|
|
40
|
+
Copied: string;
|
|
41
|
+
Save: string;
|
|
42
|
+
Cancel: string;
|
|
43
|
+
Close: string;
|
|
44
|
+
Remove: string;
|
|
45
|
+
Delete: string;
|
|
46
|
+
Edit: string;
|
|
47
|
+
Add: string;
|
|
48
|
+
Send: string;
|
|
49
|
+
Retry: string;
|
|
50
|
+
Back: string;
|
|
51
|
+
"Next page": string;
|
|
52
|
+
"Previous page": string;
|
|
53
|
+
"Show more": string;
|
|
54
|
+
Panels: string;
|
|
55
|
+
"Reset layout": string;
|
|
56
|
+
Show: string;
|
|
57
|
+
Hide: string;
|
|
58
|
+
Collapse: string;
|
|
59
|
+
Expand: string;
|
|
60
|
+
"Move up": string;
|
|
61
|
+
"Move down": string;
|
|
62
|
+
Install: string;
|
|
63
|
+
"TV mode": string;
|
|
64
|
+
On: string;
|
|
65
|
+
Off: string;
|
|
66
|
+
Account: string;
|
|
67
|
+
"Sign in": string;
|
|
68
|
+
"Sign out": string;
|
|
69
|
+
"Create account": string;
|
|
70
|
+
Email: string;
|
|
71
|
+
Password: string;
|
|
72
|
+
"Forgot password?": string;
|
|
73
|
+
"Welcome back": string;
|
|
74
|
+
Profile: string;
|
|
75
|
+
Handle: string;
|
|
76
|
+
"Your servers": string;
|
|
77
|
+
Server: string;
|
|
78
|
+
Connect: string;
|
|
79
|
+
Disconnect: string;
|
|
80
|
+
Directory: string;
|
|
81
|
+
Favourites: string;
|
|
82
|
+
Follow: string;
|
|
83
|
+
Unfollow: string;
|
|
84
|
+
Notifications: string;
|
|
85
|
+
"Phone number": string;
|
|
86
|
+
"Save number": string;
|
|
87
|
+
Transcript: string;
|
|
88
|
+
Captions: string;
|
|
89
|
+
"Translate audio": string;
|
|
90
|
+
"Audio language": string;
|
|
91
|
+
"Audio options": string;
|
|
92
|
+
"Keep background sound": string;
|
|
93
|
+
"Background level": string;
|
|
94
|
+
"Speaker voices": string;
|
|
95
|
+
"Any voice": string;
|
|
96
|
+
"Male voice": string;
|
|
97
|
+
"Female voice": string;
|
|
98
|
+
"Original audio.": string;
|
|
99
|
+
"Buy translated audio": string;
|
|
100
|
+
"Audio credit": string;
|
|
101
|
+
"Buy pass": string;
|
|
102
|
+
Pass: string;
|
|
103
|
+
"Pay with": string;
|
|
104
|
+
"Check payment": string;
|
|
105
|
+
"Open checkout": string;
|
|
106
|
+
"Free sessions": string;
|
|
107
|
+
"Usage rates and expiry": string;
|
|
108
|
+
Catalogs: string;
|
|
109
|
+
"Add catalog": string;
|
|
110
|
+
"Catalog name": string;
|
|
111
|
+
Link: string;
|
|
112
|
+
"Play link": string;
|
|
113
|
+
"Go live": string;
|
|
114
|
+
Publish: string;
|
|
115
|
+
Listen: string;
|
|
116
|
+
"Live now": string;
|
|
117
|
+
Upcoming: string;
|
|
118
|
+
Parties: string;
|
|
119
|
+
"Join party": string;
|
|
120
|
+
"Invite code": string;
|
|
121
|
+
Invite: string;
|
|
122
|
+
Chat: string;
|
|
123
|
+
"Chat message": string;
|
|
124
|
+
People: string;
|
|
125
|
+
Speaker: string;
|
|
126
|
+
Speakers: string;
|
|
127
|
+
Listener: string;
|
|
128
|
+
Moderator: string;
|
|
129
|
+
"Raise hand": string;
|
|
130
|
+
"Hand raises": string;
|
|
131
|
+
Questions: string;
|
|
132
|
+
Resources: string;
|
|
133
|
+
Replay: string;
|
|
134
|
+
Schedule: string;
|
|
135
|
+
Event: string;
|
|
136
|
+
Host: string;
|
|
137
|
+
About: string;
|
|
138
|
+
Stage: string;
|
|
139
|
+
Title: string;
|
|
140
|
+
Description: string;
|
|
141
|
+
Topic: string;
|
|
142
|
+
Public: string;
|
|
143
|
+
"Anyone with the link": string;
|
|
144
|
+
"Invited people": string;
|
|
145
|
+
"Who can join": string;
|
|
146
|
+
"Date and time": string;
|
|
147
|
+
"Expected length": string;
|
|
148
|
+
Repeat: string;
|
|
149
|
+
"Does not repeat": string;
|
|
150
|
+
"Every day": string;
|
|
151
|
+
"Every week": string;
|
|
152
|
+
Homepage: string;
|
|
153
|
+
"Host name": string;
|
|
154
|
+
"Profile photo URL": string;
|
|
155
|
+
"Broadcast link": string;
|
|
156
|
+
"Edit event": string;
|
|
157
|
+
"Save changes": string;
|
|
158
|
+
"Create live": string;
|
|
159
|
+
"Schedule class": string;
|
|
160
|
+
"Start class": string;
|
|
161
|
+
"End event": string;
|
|
162
|
+
"End session": string;
|
|
163
|
+
"Session ended": string;
|
|
164
|
+
"Event ended": string;
|
|
165
|
+
Cancelled: string;
|
|
166
|
+
"Replay available": string;
|
|
167
|
+
Join: string;
|
|
168
|
+
"Create invite": string;
|
|
169
|
+
"Invite as": string;
|
|
170
|
+
"Watch now": string;
|
|
171
|
+
"Start a room": string;
|
|
172
|
+
"Recent replays": string;
|
|
173
|
+
"Your account": string;
|
|
174
|
+
"Write with AI": string;
|
|
175
|
+
"Suggested title": string;
|
|
176
|
+
"Suggested description": string;
|
|
177
|
+
"Suggested topic": string;
|
|
178
|
+
"Use these details": string;
|
|
179
|
+
"Learn something live.": string;
|
|
180
|
+
"Screen share": string;
|
|
181
|
+
"Upload and broadcast": string;
|
|
182
|
+
Help: string;
|
|
183
|
+
"Reload Player": string;
|
|
184
|
+
"Copy Bundled CLI Path": string;
|
|
185
|
+
"Developer tools": string;
|
|
186
|
+
Undo: string;
|
|
187
|
+
Redo: string;
|
|
188
|
+
Cut: string;
|
|
189
|
+
Paste: string;
|
|
190
|
+
"Select all": string;
|
|
191
|
+
Status: string;
|
|
192
|
+
"Activity log": string;
|
|
193
|
+
Admin: string;
|
|
194
|
+
"Playback controls": string;
|
|
195
|
+
"Tracks and folders": string;
|
|
196
|
+
"Original-language captions.": string;
|
|
197
|
+
"Skip to playback controls": string;
|
|
198
|
+
"Skip to playlist": string;
|
|
199
|
+
"Skip to transcript and translated audio": string;
|
|
200
|
+
};
|
|
201
|
+
export default _default;
|