nixamp 0.23.7 → 0.24.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 +79 -3
- package/dist/captions.d.ts +57 -10
- package/dist/captions.js +253 -29
- package/dist/main.js +63 -19
- package/dist/mcp.d.ts +7 -1
- package/dist/mcp.js +159 -24
- package/dist/server.d.ts +11 -0
- package/dist/server.js +353 -8
- package/dist/speech.d.ts +21 -3
- package/dist/speech.js +65 -8
- package/dist/transcribe.d.ts +70 -0
- package/dist/transcribe.js +337 -41
- package/dist/transcript-client.d.ts +81 -0
- package/dist/transcript-client.js +76 -0
- package/dist/transcript.d.ts +7 -2
- package/dist/transcript.js +73 -5
- package/dist/transcripts.d.ts +135 -0
- package/dist/transcripts.js +384 -0
- package/dist/translate-cli.d.ts +16 -0
- package/dist/translate-cli.js +97 -0
- package/dist/translate-jobs.d.ts +39 -0
- package/dist/translate-jobs.js +120 -0
- package/dist/translate.d.ts +78 -0
- package/dist/translate.js +241 -0
- package/dist/warm.d.ts +1 -0
- package/dist/warm.js +40 -0
- package/package.json +1 -1
- package/src/captions.ts +276 -29
- package/src/main.ts +63 -19
- package/src/mcp.ts +156 -21
- package/src/server.ts +352 -8
- package/src/speech.ts +103 -13
- package/src/transcribe.ts +374 -41
- package/src/transcript-client.ts +147 -0
- package/src/transcript.ts +76 -4
- package/src/transcripts.ts +462 -0
- package/src/translate-cli.ts +111 -0
- package/src/translate-jobs.ts +132 -0
- package/src/translate.ts +276 -0
- package/src/warm.ts +43 -0
- package/web/dist/assets/{hls-3VKVEQE3-CI1U7kbP.js → hls-3VKVEQE3-Dtl-3mpW.js} +1 -1
- package/web/dist/assets/index-B0h4Nexr.js +1 -0
- package/web/dist/assets/index-BTGV3Pi5.css +1 -0
- package/web/dist/assets/{mpegts-CPOYjgRP.js → mpegts-BJC48bFV.js} +1 -1
- package/web/dist/assets/{mpegts-LO6RVLD6-CE8YPjx1.js → mpegts-LO6RVLD6-CUIAB9k3.js} +1 -1
- package/web/dist/index.html +12 -8
- package/web/dist/sw.js +6 -6
- package/web/dist/assets/index-46pwGn5-.css +0 -1
- package/web/dist/assets/index-5H3sUGHu.js +0 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
function base(site) {
|
|
2
|
+
return site.replace(/\/+$/, "");
|
|
3
|
+
}
|
|
4
|
+
async function asJson(response) {
|
|
5
|
+
const body = (await response.json().catch(() => ({})));
|
|
6
|
+
if (!response.ok)
|
|
7
|
+
return { ok: false, status: response.status, error: body.error ?? `nixamp.com answered ${response.status}` };
|
|
8
|
+
return { ok: true, body };
|
|
9
|
+
}
|
|
10
|
+
function unreachable(site, error) {
|
|
11
|
+
return { ok: false, status: 0, error: `could not reach ${site}: ${error.message}` };
|
|
12
|
+
}
|
|
13
|
+
/** The transcript of some media in a language ("" for the original), if the store has one. */
|
|
14
|
+
export async function fetchTranscript(signed, id, language = "", fetcher = fetch) {
|
|
15
|
+
const url = new URL(`${base(signed.site)}/api/v1/transcripts/${encodeURIComponent(id)}`);
|
|
16
|
+
if (language)
|
|
17
|
+
url.searchParams.set("language", language);
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetcher(url.toString(), { headers: { authorization: `Bearer ${signed.token}` } });
|
|
20
|
+
return await asJson(response);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return unreachable(signed.site, error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Keep lines: appended to what the store has, or the whole thing when `complete`. */
|
|
27
|
+
export async function keepLines(signed, id, ask, fetcher = fetch) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetcher(`${base(signed.site)}/api/v1/transcripts/${encodeURIComponent(id)}/lines`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { authorization: `Bearer ${signed.token}`, "content-type": "application/json" },
|
|
32
|
+
body: JSON.stringify(ask),
|
|
33
|
+
});
|
|
34
|
+
return await asJson(response);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return unreachable(signed.site, error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Texts in another language. `from` may be "" when nixamp.com should tell. */
|
|
41
|
+
export async function translateTexts(signed, texts, from, to, fetcher = fetch) {
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetcher(`${base(signed.site)}/api/v1/translate`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { authorization: `Bearer ${signed.token}`, "content-type": "application/json" },
|
|
46
|
+
body: JSON.stringify({ texts, from, to }),
|
|
47
|
+
});
|
|
48
|
+
return await asJson(response);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return unreachable(signed.site, error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Forget some media's transcripts. Only whoever stored them may. */
|
|
55
|
+
export async function forgetTranscript(signed, id, fetcher = fetch) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetcher(`${base(signed.site)}/api/v1/transcripts/${encodeURIComponent(id)}`, {
|
|
58
|
+
method: "DELETE",
|
|
59
|
+
headers: { authorization: `Bearer ${signed.token}` },
|
|
60
|
+
});
|
|
61
|
+
return await asJson(response);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return unreachable(signed.site, error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** What this account has had written down. */
|
|
68
|
+
export async function listTranscripts(signed, fetcher = fetch) {
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetcher(`${base(signed.site)}/api/v1/transcripts`, { headers: { authorization: `Bearer ${signed.token}` } });
|
|
71
|
+
return await asJson(response);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return unreachable(signed.site, error);
|
|
75
|
+
}
|
|
76
|
+
}
|
package/dist/transcript.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { type Session } from "./session.ts";
|
|
1
2
|
export interface TranscriptLine {
|
|
2
3
|
channel: string;
|
|
3
4
|
at: number;
|
|
4
5
|
until: number;
|
|
5
6
|
text: string;
|
|
7
|
+
language?: string;
|
|
8
|
+
original?: string;
|
|
6
9
|
}
|
|
7
10
|
export interface TranscriptAnswer {
|
|
8
11
|
channel: string;
|
|
@@ -26,8 +29,8 @@ export type Fetched = {
|
|
|
26
29
|
export declare function readTranscript(target: {
|
|
27
30
|
url: string;
|
|
28
31
|
key: string | null;
|
|
29
|
-
}, channel: string, after?: number, fetcher?: typeof fetch): Promise<Fetched>;
|
|
30
|
-
/** A line as the terminal prints it: the time its sound was heard, then the words. */
|
|
32
|
+
}, channel: string, after?: number, fetcher?: typeof fetch, language?: string): Promise<Fetched>;
|
|
33
|
+
/** A line as the terminal prints it: the time its sound was heard, then the words; a translation says its language. */
|
|
31
34
|
export declare function printed(line: TranscriptLine): string;
|
|
32
35
|
export interface TranscriptDeps {
|
|
33
36
|
fetcher?: typeof fetch;
|
|
@@ -36,5 +39,7 @@ export interface TranscriptDeps {
|
|
|
36
39
|
/** How many asks --follow makes before it stops. Forever, except in the tests. */
|
|
37
40
|
polls?: number;
|
|
38
41
|
sleep?: (ms: number) => Promise<void>;
|
|
42
|
+
/** The sign-in, for what nixamp.com keeps; the tests hand one in. */
|
|
43
|
+
session?: Pick<Session, "site" | "token"> | null;
|
|
39
44
|
}
|
|
40
45
|
export declare function transcript(argv: string[], deps?: TranscriptDeps): Promise<number>;
|
package/dist/transcript.js
CHANGED
|
@@ -12,24 +12,35 @@
|
|
|
12
12
|
* share link, or the daemon on this machine when neither is given.
|
|
13
13
|
*/
|
|
14
14
|
import { resolveTarget } from "./admin.js";
|
|
15
|
+
import { readSession } from "./session.js";
|
|
15
16
|
import { KEY_HEADER } from "./share.js";
|
|
17
|
+
import { awaitTranscript, rendered } from "./transcribe.js";
|
|
18
|
+
import { listTranscripts } from "./transcript-client.js";
|
|
19
|
+
import { idFrom, languageCode } from "./transcripts.js";
|
|
16
20
|
const HELP = `nixamp transcript — what a channel is saying, written down.
|
|
17
21
|
|
|
18
22
|
nixamp transcript --channel ID the recent lines from this machine's daemon
|
|
19
23
|
nixamp transcript --url URL --key K --channel ID from another server, with its share link
|
|
20
24
|
nixamp transcript ... --follow and keep printing as it speaks
|
|
25
|
+
nixamp transcript ... --language sv the lines in Swedish, translated as they are said
|
|
21
26
|
nixamp transcript ... --json the lines as JSON
|
|
27
|
+
nixamp transcript --kept MEDIA_OR_ID [--language de] [--srt|--vtt|--txt]
|
|
28
|
+
a transcript nixamp.com keeps: a file's, a link's, a past live's
|
|
29
|
+
nixamp transcript --list what this account has had written down
|
|
22
30
|
|
|
23
31
|
ID is the channel's id as the server names it (the address bar says it when
|
|
24
32
|
you are watching one). The server captions a channel while somebody is asking
|
|
25
33
|
for the transcript, with nixamp.com's ear; it needs an ffmpeg and a sign-in
|
|
26
|
-
(\`nixamp login\`) on that server.
|
|
34
|
+
(\`nixamp login\`) on that server. What it hears is kept on nixamp.com under
|
|
35
|
+
what the channel is playing, and read back from there the next time.
|
|
27
36
|
`;
|
|
28
37
|
/** One ask for a channel's recent lines, after a moment when given. */
|
|
29
|
-
export async function readTranscript(target, channel, after = 0, fetcher = fetch) {
|
|
38
|
+
export async function readTranscript(target, channel, after = 0, fetcher = fetch, language = "") {
|
|
30
39
|
const url = new URL(`${target.url.replace(/\/+$/, "")}/api/channels/${encodeURIComponent(channel)}/transcript`);
|
|
31
40
|
if (after > 0)
|
|
32
41
|
url.searchParams.set("after", String(after));
|
|
42
|
+
if (language)
|
|
43
|
+
url.searchParams.set("language", language);
|
|
33
44
|
let response;
|
|
34
45
|
try {
|
|
35
46
|
response = await fetcher(url.toString(), { headers: target.key ? { [KEY_HEADER]: target.key } : {} });
|
|
@@ -54,21 +65,78 @@ export async function readTranscript(target, channel, after = 0, fetcher = fetch
|
|
|
54
65
|
},
|
|
55
66
|
};
|
|
56
67
|
}
|
|
57
|
-
/** A line as the terminal prints it: the time its sound was heard, then the words. */
|
|
68
|
+
/** A line as the terminal prints it: the time its sound was heard, then the words; a translation says its language. */
|
|
58
69
|
export function printed(line) {
|
|
59
70
|
const at = new Date(line.at);
|
|
60
71
|
const clock = Number.isNaN(at.getTime()) ? "--:--:--" : at.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
61
|
-
return `${clock} ${line.text}`;
|
|
72
|
+
return `${clock} ${line.original !== undefined && line.language ? `[${line.language}] ` : ""}${line.text}`;
|
|
62
73
|
}
|
|
63
74
|
function flag(argv, name) {
|
|
64
75
|
const at = argv.indexOf(name);
|
|
65
76
|
return at === -1 ? undefined : argv[at + 1];
|
|
66
77
|
}
|
|
78
|
+
/** `--kept` and `--list`: what nixamp.com keeps, rather than what a server is saying now. */
|
|
79
|
+
async function kept(argv, deps, language) {
|
|
80
|
+
const session = deps.session === undefined ? readSession() : deps.session;
|
|
81
|
+
if (session === null) {
|
|
82
|
+
console.error("nixamp: not signed in. Try `nixamp login`.");
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
const fetcher = deps.fetcher ?? fetch;
|
|
86
|
+
if (argv.includes("--list")) {
|
|
87
|
+
const got = await listTranscripts(session, fetcher);
|
|
88
|
+
if (!got.ok) {
|
|
89
|
+
console.error(`nixamp: ${got.error}`);
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
if (argv.includes("--json")) {
|
|
93
|
+
console.log(JSON.stringify(got.body.transcripts, null, 2));
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
if (got.body.transcripts.length === 0) {
|
|
97
|
+
console.log("Nothing written down yet. `nixamp transcribe FILE` keeps a file; a captioned channel keeps itself.");
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
for (const one of got.body.transcripts) {
|
|
101
|
+
console.log(`${one.id} ${(one.language || "?").padEnd(2)}${one.translatedFrom ? `<${one.translatedFrom}` : " "} ${String(one.lines).padStart(5)} lines${one.complete ? " " : "+"} ${one.title || one.media}`);
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
const named = flag(argv, "--kept") ?? "";
|
|
106
|
+
if (!named) {
|
|
107
|
+
console.error("nixamp: --kept needs the transcript's id, or the media identity.");
|
|
108
|
+
return 64;
|
|
109
|
+
}
|
|
110
|
+
const got = await awaitTranscript(session, idFrom(named), language, {
|
|
111
|
+
fetcher, onProgress: (line) => console.error(line), ...(deps.sleep ? { sleep: deps.sleep } : {}), ...(deps.polls !== undefined ? { polls: deps.polls } : {}),
|
|
112
|
+
});
|
|
113
|
+
if (!got.ok) {
|
|
114
|
+
console.error(`nixamp: ${got.error}`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
if (got.body.translating) {
|
|
118
|
+
console.error(`nixamp: still being translated (${got.body.translating.done} of ${got.body.translating.total}); ask again in a moment.`);
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
if (argv.includes("--json")) {
|
|
122
|
+
console.log(JSON.stringify(got.body, null, 2));
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
console.log(rendered(got.body.lines, argv.includes("--srt") ? "srt" : argv.includes("--vtt") ? "vtt" : argv.includes("--txt") ? "txt" : "lines"));
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
67
128
|
export async function transcript(argv, deps = {}) {
|
|
68
129
|
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
69
130
|
console.log(HELP);
|
|
70
131
|
return 0;
|
|
71
132
|
}
|
|
133
|
+
const language = languageCode(flag(argv, "--language"));
|
|
134
|
+
if (language === null) {
|
|
135
|
+
console.error("nixamp: --language is a two-letter code, such as sv.");
|
|
136
|
+
return 64;
|
|
137
|
+
}
|
|
138
|
+
if (argv.includes("--kept") || argv.includes("--list"))
|
|
139
|
+
return kept(argv, deps, language);
|
|
72
140
|
let target;
|
|
73
141
|
try {
|
|
74
142
|
target = resolveTarget(argv);
|
|
@@ -85,7 +153,7 @@ export async function transcript(argv, deps = {}) {
|
|
|
85
153
|
let after = 0;
|
|
86
154
|
let polls = 0;
|
|
87
155
|
for (;;) {
|
|
88
|
-
const got = await readTranscript(target, channel, after, fetcher);
|
|
156
|
+
const got = await readTranscript(target, channel, after, fetcher, language);
|
|
89
157
|
if (!got.ok) {
|
|
90
158
|
console.error(`nixamp: ${got.error}`);
|
|
91
159
|
return 1;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Queryable } from "./follows.ts";
|
|
2
|
+
/** One thing said: seconds into the media, and the words. */
|
|
3
|
+
export interface TranscriptLine {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
export type MediaKind = "file" | "url" | "live";
|
|
9
|
+
export interface Transcript {
|
|
10
|
+
/** sha256 of the media identity: what the routes are addressed by. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** The identity itself: file:v1:<hex>, url:<address>, live:<server>/<channel>@<started>. */
|
|
13
|
+
media: string;
|
|
14
|
+
kind: MediaKind;
|
|
15
|
+
/** ISO 639-1 of the lines, or "" when the ear was not told and did not say. */
|
|
16
|
+
language: string;
|
|
17
|
+
/** The language this was translated from, or null for what was heard. */
|
|
18
|
+
translatedFrom: string | null;
|
|
19
|
+
model: string;
|
|
20
|
+
/** Whether the whole media was heard in one pass, rather than in windows as it played. */
|
|
21
|
+
complete: boolean;
|
|
22
|
+
/** What it was called when it was heard, for a list. */
|
|
23
|
+
title: string;
|
|
24
|
+
/** Who stored it: the account the ear was asked as. */
|
|
25
|
+
by: string;
|
|
26
|
+
/** How far into the media the lines reach, seconds. */
|
|
27
|
+
seconds: number;
|
|
28
|
+
lines: TranscriptLine[];
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
}
|
|
31
|
+
/** A transcript without its lines, as a list shows it. */
|
|
32
|
+
export type TranscriptSummary = Omit<Transcript, "lines"> & {
|
|
33
|
+
lines: number;
|
|
34
|
+
};
|
|
35
|
+
/** A row grows no further than this; a day of talk is under half of it. */
|
|
36
|
+
export declare const MAX_LINES = 20000;
|
|
37
|
+
/** A line longer than this is not a line. */
|
|
38
|
+
export declare const MAX_LINE_CHARS = 2000;
|
|
39
|
+
/** How much of a file's bytes go into its fingerprint, at each end. */
|
|
40
|
+
export declare const FINGERPRINT_BYTES: number;
|
|
41
|
+
/**
|
|
42
|
+
* A file's identity from its size and a megabyte at each end. Hashing every
|
|
43
|
+
* byte of a film takes seconds a captioner does not have at start, and the
|
|
44
|
+
* ends plus the size tell two files apart as surely as anything short of
|
|
45
|
+
* the whole does. The v1 says how it was made, so a better way later does
|
|
46
|
+
* not collide with this one.
|
|
47
|
+
*/
|
|
48
|
+
export declare function fileFingerprint(path: string): string;
|
|
49
|
+
/** A link's identity: the address without its fragment, which no server sees. */
|
|
50
|
+
export declare function mediaOfUrl(url: string): string;
|
|
51
|
+
/** A broadcast's identity: the server, the channel, and when it began, so a second airing is a second transcript. */
|
|
52
|
+
export declare function mediaOfLive(server: string, channel: string, startedAt: number): string;
|
|
53
|
+
export declare function kindOf(media: string): MediaKind;
|
|
54
|
+
/** The id a transcript is addressed by: a hash, so a link with a key in it is not in the address bar. */
|
|
55
|
+
export declare function transcriptIdOf(media: string): string;
|
|
56
|
+
/** A transcript id as a request names one, or null when it is not one. */
|
|
57
|
+
export declare function transcriptId(value: unknown): string | null;
|
|
58
|
+
/** Either the id itself or a media identity, as a route accepts both. */
|
|
59
|
+
export declare function idFrom(value: string): string;
|
|
60
|
+
/** Lines as a request or a row hands them in: only the well-formed ones, tidied. */
|
|
61
|
+
export declare function linesFrom(value: unknown): TranscriptLine[];
|
|
62
|
+
/**
|
|
63
|
+
* Lines in order, with the duplicates gone. Two servers playing one file
|
|
64
|
+
* both append what they heard, a captioner that restarted hears a window
|
|
65
|
+
* again: the second copy of a moment says the same thing, later, and a
|
|
66
|
+
* viewer would read it twice. A line whose span mostly overlaps one already
|
|
67
|
+
* kept is that second copy.
|
|
68
|
+
*/
|
|
69
|
+
export declare function mergeLines(lines: TranscriptLine[]): TranscriptLine[];
|
|
70
|
+
/** Whether the kept lines already say what this span says: over half of it is covered. */
|
|
71
|
+
export declare function covered(lines: TranscriptLine[], start: number, end: number): TranscriptLine[];
|
|
72
|
+
/** The line for a moment, if there is one: the one that begins nearest, within a second. */
|
|
73
|
+
export declare function lineAt(lines: TranscriptLine[], start: number): TranscriptLine | null;
|
|
74
|
+
/** How far the lines reach, seconds. */
|
|
75
|
+
export declare function reach(lines: TranscriptLine[]): number;
|
|
76
|
+
/** Seconds as a subtitle clock: 01:02:03,456 for SRT, 01:02:03.456 for VTT. */
|
|
77
|
+
export declare function stamp(seconds: number, separator?: "," | "."): string;
|
|
78
|
+
export declare function toSrt(lines: TranscriptLine[]): string;
|
|
79
|
+
export declare function toVtt(lines: TranscriptLine[]): string;
|
|
80
|
+
/** The words alone, one line each, as a person reads them. */
|
|
81
|
+
export declare function toText(lines: TranscriptLine[]): string;
|
|
82
|
+
export type Format = "json" | "srt" | "vtt" | "txt";
|
|
83
|
+
export declare function formatOf(value: unknown): Format | null;
|
|
84
|
+
/** A two-letter language, or "" for none; null when the value is something else. */
|
|
85
|
+
export declare function languageCode(value: unknown): string | null;
|
|
86
|
+
export interface SaveAsk {
|
|
87
|
+
media: string;
|
|
88
|
+
language: string;
|
|
89
|
+
translatedFrom?: string | null;
|
|
90
|
+
model?: string;
|
|
91
|
+
/** A whole-media pass: these lines are the transcript, not part of it. */
|
|
92
|
+
complete?: boolean;
|
|
93
|
+
title?: string;
|
|
94
|
+
by: string;
|
|
95
|
+
lines: TranscriptLine[];
|
|
96
|
+
}
|
|
97
|
+
export declare class Transcripts {
|
|
98
|
+
private readonly db;
|
|
99
|
+
private readonly onEvent;
|
|
100
|
+
private ready;
|
|
101
|
+
constructor(db: Queryable, onEvent?: (message: string) => void);
|
|
102
|
+
private ensure;
|
|
103
|
+
/**
|
|
104
|
+
* Keep lines. New media is a new row; known media grows by these lines,
|
|
105
|
+
* unless the row is already whole -- then pieces are nothing it needs --
|
|
106
|
+
* or these lines are the whole, which replaces whatever pieces there
|
|
107
|
+
* were. Answers the row as it now stands.
|
|
108
|
+
*/
|
|
109
|
+
save(ask: SaveAsk): Promise<Transcript | null>;
|
|
110
|
+
/**
|
|
111
|
+
* The transcript of some media in a language: the original when none is
|
|
112
|
+
* named, which is the heard row -- the whole one when there is one.
|
|
113
|
+
*/
|
|
114
|
+
get(id: string, language?: string): Promise<Transcript | null>;
|
|
115
|
+
/** Every language some media has been written down in, with how much of it. */
|
|
116
|
+
languages(id: string): Promise<{
|
|
117
|
+
language: string;
|
|
118
|
+
translatedFrom: string | null;
|
|
119
|
+
lines: number;
|
|
120
|
+
complete: boolean;
|
|
121
|
+
}[]>;
|
|
122
|
+
/** What an account has had written down, newest first. */
|
|
123
|
+
list(by: string, limit?: number): Promise<TranscriptSummary[]>;
|
|
124
|
+
/** Forget some media, in every language. Only whoever stored it may. Says whether anything went. */
|
|
125
|
+
forget(id: string, by: string): Promise<boolean>;
|
|
126
|
+
/** Never throws: a store that is having a moment costs the memory, not the request. */
|
|
127
|
+
quietly<T>(what: string, run: () => Promise<T>): Promise<T | null>;
|
|
128
|
+
}
|
|
129
|
+
/** A transcript as the routes answer it, with or without its lines. */
|
|
130
|
+
export declare function wire(transcript: Transcript, languages?: {
|
|
131
|
+
language: string;
|
|
132
|
+
translatedFrom: string | null;
|
|
133
|
+
lines: number;
|
|
134
|
+
complete: boolean;
|
|
135
|
+
}[]): Record<string, unknown>;
|