@wrenautomation/read 0.0.0-stage → 0.29.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/LICENSE.md +61 -0
- package/README.md +36 -2
- package/dist/fetch/fetcher.d.ts +84 -0
- package/dist/fetch/fetcher.js +180 -0
- package/dist/fetch/fetcher.js.map +1 -0
- package/dist/fetch/index.d.ts +9 -0
- package/dist/fetch/index.js +10 -0
- package/dist/fetch/index.js.map +1 -0
- package/dist/fetch/ladder.d.ts +44 -0
- package/dist/fetch/ladder.js +18 -0
- package/dist/fetch/ladder.js.map +1 -0
- package/dist/fetch/robots.d.ts +14 -0
- package/dist/fetch/robots.js +92 -0
- package/dist/fetch/robots.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/study/claims.d.ts +19 -0
- package/dist/study/claims.js +13 -0
- package/dist/study/claims.js.map +1 -0
- package/dist/study/drafts.d.ts +43 -0
- package/dist/study/drafts.js +93 -0
- package/dist/study/drafts.js.map +1 -0
- package/dist/study/evidence.d.ts +17 -0
- package/dist/study/evidence.js +72 -0
- package/dist/study/evidence.js.map +1 -0
- package/dist/study/grounding.d.ts +29 -0
- package/dist/study/grounding.js +58 -0
- package/dist/study/grounding.js.map +1 -0
- package/dist/study/index.d.ts +11 -0
- package/dist/study/index.js +12 -0
- package/dist/study/index.js.map +1 -0
- package/dist/study/report.d.ts +55 -0
- package/dist/study/report.js +56 -0
- package/dist/study/report.js.map +1 -0
- package/dist/text/entities.d.ts +2 -0
- package/dist/text/entities.js +4 -0
- package/dist/text/entities.js.map +1 -0
- package/dist/text/htmltext.d.ts +30 -0
- package/dist/text/htmltext.js +208 -0
- package/dist/text/htmltext.js.map +1 -0
- package/dist/text/index.d.ts +7 -0
- package/dist/text/index.js +8 -0
- package/dist/text/index.js.map +1 -0
- package/dist/text/text.d.ts +8 -0
- package/dist/text/text.js +38 -0
- package/dist/text/text.js.map +1 -0
- package/dist/video/gemini.d.ts +46 -0
- package/dist/video/gemini.js +93 -0
- package/dist/video/gemini.js.map +1 -0
- package/dist/video/index.d.ts +39 -0
- package/dist/video/index.js +16 -0
- package/dist/video/index.js.map +1 -0
- package/dist/video/list.d.ts +35 -0
- package/dist/video/list.js +32 -0
- package/dist/video/list.js.map +1 -0
- package/dist/video/read.d.ts +53 -0
- package/dist/video/read.js +92 -0
- package/dist/video/read.js.map +1 -0
- package/dist/video/shots.d.ts +35 -0
- package/dist/video/shots.js +139 -0
- package/dist/video/shots.js.map +1 -0
- package/dist/video/tools.d.ts +16 -0
- package/dist/video/tools.js +29 -0
- package/dist/video/tools.js.map +1 -0
- package/dist/video/url.d.ts +73 -0
- package/dist/video/url.js +255 -0
- package/dist/video/url.js.map +1 -0
- package/dist/video/watch.d.ts +94 -0
- package/dist/video/watch.js +321 -0
- package/dist/video/watch.js.map +1 -0
- package/package.json +55 -3
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A video or podcast on the web read as one markdown document (`VideoRead`). Four
|
|
3
|
+
* readers: a YouTube video from its captions and chapters (yt-dlp) plus a video model's read of
|
|
4
|
+
* the screen; a YouTube video from its URL alone (the model hears and reads it: no download, no
|
|
5
|
+
* home IP); any other video yt-dlp fetches (`watchFile`: an ear and an eye); a podcast
|
|
6
|
+
* episode from its audio URL (fetched, then `hearAudio`). Everything that takes a URL or
|
|
7
|
+
* spawns yt-dlp lives here; the reading over a file is in `watch.ts` and `read.ts`. The video
|
|
8
|
+
* model is Gemini (`geminiVideo`): the one that takes a YouTube URL. Every caller passes its own models
|
|
9
|
+
* and tools. Nothing here touches a database.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { readMedia, SHOWN } from "./gemini.js";
|
|
16
|
+
import { CLIP_S, captionsBody, hearAudio, isoDate, real, } from "./read.js";
|
|
17
|
+
import { clock, runFor, TOOL_MS } from "./tools.js";
|
|
18
|
+
import { renderWatched, watchFile } from "./watch.js";
|
|
19
|
+
/** yt-dlp's download of a reel or video: 540p on the short side, remuxed to mp4. */
|
|
20
|
+
export async function download(url, ytDlp, dir) {
|
|
21
|
+
const [cmd, ...pre] = ytDlp.split(/\s+/);
|
|
22
|
+
const { stdout } = await runFor(cmd, [
|
|
23
|
+
...pre,
|
|
24
|
+
...["-j", "--no-simulate", "--no-warnings", "--no-playlist"],
|
|
25
|
+
// `res` is the short side, so a vertical reel gets 540x960, not its 1080x1920.
|
|
26
|
+
...["-S", "res:540", "-f", "bv*+ba/b", "--remux-video", "mp4"],
|
|
27
|
+
...["-o", join(dir, "video.%(ext)s"), url],
|
|
28
|
+
], TOOL_MS, { maxBuffer: 256 * 1024 * 1024 });
|
|
29
|
+
const file = (await readdir(dir)).find((f) => f.endsWith(".mp4"));
|
|
30
|
+
if (!file)
|
|
31
|
+
throw new Error(`${url}: yt-dlp wrote no mp4`);
|
|
32
|
+
return { file: join(dir, file), info: JSON.parse(stdout) };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Watch a video: a URL yt-dlp fetches first (`ytDlp` is the command, split on spaces:
|
|
36
|
+
* `uvx yt-dlp`), or a file on disk, which goes straight to `watchFile`. A URL's yt-dlp
|
|
37
|
+
* metadata comes back as `info`; a duration ffmpeg could not read falls back to it.
|
|
38
|
+
*/
|
|
39
|
+
export async function watch(input, opts) {
|
|
40
|
+
if (!/^https?:\/\//.test(input))
|
|
41
|
+
return watchFile(input, opts);
|
|
42
|
+
const dir = await mkdtemp(join(tmpdir(), "read-watch-"));
|
|
43
|
+
try {
|
|
44
|
+
const { file, info } = await download(input, opts.ytDlp ?? "yt-dlp", dir);
|
|
45
|
+
const watched = await watchFile(file, opts);
|
|
46
|
+
return { ...watched, durationS: watched.durationS || info.duration || 0, info };
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await rm(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const captionTracks = (info) => [info.subtitles?.en, info.automatic_captions?.en, info.automatic_captions?.["en-orig"]]
|
|
53
|
+
.flatMap((t) => t ?? [])
|
|
54
|
+
.filter((t) => t.ext === "json3");
|
|
55
|
+
const SHOWN_ASK = (start, paraphrase) => `${SHOWN(start, paraphrase)} Markdown. If nothing is shown, reply with the single word none.`;
|
|
56
|
+
/** A YouTube page as a video part; `clip` reads only that stretch, at half a frame a second. */
|
|
57
|
+
const youtubePart = (page, start, durationS) => ({
|
|
58
|
+
type: "video",
|
|
59
|
+
data: new URL(page),
|
|
60
|
+
mediaType: "video/mp4",
|
|
61
|
+
...(start === null
|
|
62
|
+
? {}
|
|
63
|
+
: { clip: { startS: start, endS: Math.min(start + CLIP_S, durationS), fps: 0.5 } }),
|
|
64
|
+
});
|
|
65
|
+
// Low resolution reads a shown document fine at a quarter of the tokens.
|
|
66
|
+
const LOW = { resolution: "low" };
|
|
67
|
+
/**
|
|
68
|
+
* What the video shows on screen (documents, slides, tables, templates), read by `model` straight
|
|
69
|
+
* from the YouTube URL in `CLIP_S` clips. One markdown block per clip, timestamps absolute.
|
|
70
|
+
* Empty with no model.
|
|
71
|
+
*/
|
|
72
|
+
async function screenText(url, durationS, model) {
|
|
73
|
+
if (!model)
|
|
74
|
+
return "";
|
|
75
|
+
const clips = Array.from({ length: Math.ceil(durationS / CLIP_S) }, (_, i) => i * CLIP_S);
|
|
76
|
+
const read = async (start) => {
|
|
77
|
+
try {
|
|
78
|
+
return await readMedia(model, [youtubePart(url, start, durationS)], (paraphrase) => SHOWN_ASK(start, paraphrase), LOW);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
// One unread clip must not lose the other 23: leave a marker and carry on.
|
|
82
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
83
|
+
// The document is written all the same: its text keeps the marker below at this time.
|
|
84
|
+
console.warn(`${url} ${clock(start)}: a clip left unread by ${model.id} (${why})`);
|
|
85
|
+
return `[${clock(start)}] (not read: ${why})`;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const out = [];
|
|
89
|
+
// 4 clips at a time; limits are per key, so go wider if it ever drags.
|
|
90
|
+
for (let i = 0; i < clips.length; i += 4) {
|
|
91
|
+
const texts = await Promise.all(clips.slice(i, i + 4).map(read));
|
|
92
|
+
out.push(...texts.filter(real));
|
|
93
|
+
}
|
|
94
|
+
return out.join("\n\n");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A YouTube video from its captions and chapters, through `yt-dlp` (`ytDlp` is the command,
|
|
98
|
+
* split on spaces: `uvx yt-dlp`). With a `video` model, an `## On screen` section follows: what
|
|
99
|
+
* the video showed, read from the frames, since the captions carry only the speech.
|
|
100
|
+
*/
|
|
101
|
+
export async function readYoutube(url, opts) {
|
|
102
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
103
|
+
const [cmd, ...pre] = opts.ytDlp.split(/\s+/);
|
|
104
|
+
const { stdout } = await runFor(cmd, [...pre, "-J", "--no-warnings", "--no-playlist", url], TOOL_MS, { maxBuffer: 256 * 1024 * 1024 });
|
|
105
|
+
const info = JSON.parse(stdout);
|
|
106
|
+
const tracks = captionTracks(info);
|
|
107
|
+
if (!tracks.length)
|
|
108
|
+
throw new Error(`${url}: no English captions (manual or auto) to read`);
|
|
109
|
+
// YouTube throttles single caption URLs (429); the same text sits behind several, so try each.
|
|
110
|
+
let json;
|
|
111
|
+
let last = 0;
|
|
112
|
+
for (const t of tracks) {
|
|
113
|
+
const res = await fetchFn(t.url);
|
|
114
|
+
last = res.status;
|
|
115
|
+
if (!res.ok)
|
|
116
|
+
continue;
|
|
117
|
+
json = (await res.json());
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
if (!json)
|
|
121
|
+
throw new Error(`${url}: captions fetch HTTP ${last} on all ${tracks.length} tracks`);
|
|
122
|
+
let body = captionsBody(info, json);
|
|
123
|
+
const screen = await screenText(url, info.duration ?? 0, opts.video);
|
|
124
|
+
if (screen)
|
|
125
|
+
body += `\n## On screen\n\nWhat the video showed (documents, slides, tables, visuals), read from the frames; the transcript above has only the speech.\n\n${screen}\n`;
|
|
126
|
+
const uploaded = isoDate(info.upload_date);
|
|
127
|
+
return {
|
|
128
|
+
kind: "youtube",
|
|
129
|
+
id: info.id,
|
|
130
|
+
title: info.title,
|
|
131
|
+
url: info.webpage_url ?? `https://www.youtube.com/watch?v=${info.id}`,
|
|
132
|
+
...(info.channel ? { channel: info.channel } : {}),
|
|
133
|
+
...(uploaded ? { uploaded } : {}),
|
|
134
|
+
...(info.duration ? { duration: Math.round(info.duration) } : {}),
|
|
135
|
+
...(info.thumbnail ? { thumbnail: info.thumbnail } : {}),
|
|
136
|
+
body,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const SPEECH = "Transcribe the speech in this video: every word spoken, in paragraphs, each starting with its [m:ss] video time. Fix only obvious mishearings. Markdown, no heading. If nobody speaks, reply with the single word none.";
|
|
140
|
+
/**
|
|
141
|
+
* A YouTube video read from its URL alone, no download and no home IP: `model` hears the speech
|
|
142
|
+
* and reads the screen in `CLIP_S` clips (one clip when the length isn't known). The title,
|
|
143
|
+
* channel and thumbnail come from YouTube's oEmbed. For a server; a machine with yt-dlp and a
|
|
144
|
+
* home IP reads the captions instead (`readYoutube`).
|
|
145
|
+
*/
|
|
146
|
+
export async function readYoutubeByUrl(url, opts) {
|
|
147
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
148
|
+
const id = /(?:v=|youtu\.be\/|shorts\/|live\/)([\w-]{11})/.exec(url)?.[1];
|
|
149
|
+
if (!id)
|
|
150
|
+
throw new Error(`${url}: not a YouTube video`);
|
|
151
|
+
const page = `https://www.youtube.com/watch?v=${id}`;
|
|
152
|
+
const meta = (await fetchFn(`https://www.youtube.com/oembed?format=json&url=${encodeURIComponent(page)}`)
|
|
153
|
+
.then((r) => (r.ok ? r.json() : {}))
|
|
154
|
+
.catch(() => ({})));
|
|
155
|
+
const duration = opts.durationS ?? 0;
|
|
156
|
+
const starts = duration
|
|
157
|
+
? Array.from({ length: Math.ceil(duration / CLIP_S) }, (_, i) => i * CLIP_S)
|
|
158
|
+
: [null];
|
|
159
|
+
const readClip = async (start) => {
|
|
160
|
+
const media = [youtubePart(page, start, duration)];
|
|
161
|
+
const label = `${opts.model.id} ${id} ${clock(start ?? 0)}`;
|
|
162
|
+
const [said, screen] = await Promise.all([
|
|
163
|
+
readMedia(opts.model, media, SPEECH, LOW).catch((err) => {
|
|
164
|
+
throw new Error(`${label} speech: ${err instanceof Error ? err.message : err}`, {
|
|
165
|
+
cause: err,
|
|
166
|
+
});
|
|
167
|
+
}),
|
|
168
|
+
// The screen is extra: a clip it cannot read keeps its speech.
|
|
169
|
+
readMedia(opts.model, media, (p) => SHOWN_ASK(start ?? 0, p), LOW).catch((err) => {
|
|
170
|
+
console.warn(`${label} screen: unread, ${err instanceof Error ? err.message : err}`);
|
|
171
|
+
return "";
|
|
172
|
+
}),
|
|
173
|
+
]);
|
|
174
|
+
return { said, screen };
|
|
175
|
+
};
|
|
176
|
+
const clips = [];
|
|
177
|
+
// 4 clips at a time, kept in order: a key fleet spreads them.
|
|
178
|
+
for (let i = 0; i < starts.length; i += 4)
|
|
179
|
+
clips.push(...(await Promise.all(starts.slice(i, i + 4).map(readClip))));
|
|
180
|
+
const speech = clips.map((c) => c.said).filter(real);
|
|
181
|
+
const shown = clips.map((c) => c.screen).filter(real);
|
|
182
|
+
return {
|
|
183
|
+
kind: "youtube",
|
|
184
|
+
id,
|
|
185
|
+
title: meta.title?.trim() || page,
|
|
186
|
+
url: page,
|
|
187
|
+
...(meta.author_name ? { channel: meta.author_name } : {}),
|
|
188
|
+
...(duration ? { duration: Math.round(duration) } : {}),
|
|
189
|
+
...(meta.thumbnail_url ? { thumbnail: meta.thumbnail_url } : {}),
|
|
190
|
+
body: `## Speech\n\n${speech.join("\n\n") || "(no speech)"}\n${shown.length ? `\n## On screen\n\n${shown.join("\n\n")}\n` : ""}`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Any other video `yt-dlp` fetches (an Instagram reel, TikTok, X). No captions there, so
|
|
195
|
+
* `watch` hears the audio and reads the frames (its `video` model adds a read of the whole
|
|
196
|
+
* screen when given). The body holds the caption, then Speech, On screen and Cuts.
|
|
197
|
+
*/
|
|
198
|
+
export async function readVideo(url, opts) {
|
|
199
|
+
const watched = await watch(url, opts);
|
|
200
|
+
const info = watched.info;
|
|
201
|
+
const caption = info.description?.trim() ?? "";
|
|
202
|
+
const by = info.channel ?? info.uploader;
|
|
203
|
+
const uploaded = isoDate(info.upload_date);
|
|
204
|
+
const duration = info.duration ?? watched.durationS;
|
|
205
|
+
return {
|
|
206
|
+
kind: (info.extractor_key ?? "video").toLowerCase(),
|
|
207
|
+
id: info.id,
|
|
208
|
+
title: caption.split("\n")[0]?.slice(0, 100) || info.title,
|
|
209
|
+
url: info.webpage_url ?? url,
|
|
210
|
+
...(by ? { channel: by } : {}),
|
|
211
|
+
...(uploaded ? { uploaded } : {}),
|
|
212
|
+
...(duration ? { duration: Math.round(duration) } : {}),
|
|
213
|
+
...(info.thumbnail ? { thumbnail: info.thumbnail } : {}),
|
|
214
|
+
body: `${caption ? `## Caption\n\n${caption}\n\n` : ""}${renderWatched(watched)}\n`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const AUDIO_MAX = 600_000_000;
|
|
218
|
+
/**
|
|
219
|
+
* A podcast episode heard from its audio URL: downloaded to a temp file, then
|
|
220
|
+
* `hearAudio` (ffmpeg clips, `model` transcribes each). `url` is the episode's page; `title` and
|
|
221
|
+
* `creator` are what its feed said. The id is a hash of the audio URL.
|
|
222
|
+
*/
|
|
223
|
+
export async function readAudio(audioUrl, opts) {
|
|
224
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
225
|
+
const dir = await mkdtemp(join(tmpdir(), "read-audio-"));
|
|
226
|
+
try {
|
|
227
|
+
const res = await fetchFn(audioUrl, { signal: AbortSignal.timeout(600_000) });
|
|
228
|
+
if (!res.ok || !res.body)
|
|
229
|
+
throw new Error(`${audioUrl}: HTTP ${res.status}`);
|
|
230
|
+
if (Number(res.headers.get("content-length") ?? 0) > AUDIO_MAX)
|
|
231
|
+
throw new Error(`${audioUrl}: over ${AUDIO_MAX / 1e6} MB`);
|
|
232
|
+
const whole = join(dir, "episode");
|
|
233
|
+
await writeFile(whole, Buffer.from(await res.arrayBuffer()));
|
|
234
|
+
const id = createHash("sha1").update(audioUrl).digest("hex").slice(0, 12);
|
|
235
|
+
const heard = await hearAudio(whole, {
|
|
236
|
+
model: opts.model,
|
|
237
|
+
...(opts.ffmpeg ? { ffmpeg: opts.ffmpeg } : {}),
|
|
238
|
+
...(opts.clipS ? { clipS: opts.clipS } : {}),
|
|
239
|
+
label: id,
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
kind: "podcast",
|
|
243
|
+
id,
|
|
244
|
+
title: opts.title?.trim() || opts.url,
|
|
245
|
+
url: opts.url,
|
|
246
|
+
...(opts.creator ? { channel: opts.creator } : {}),
|
|
247
|
+
...(heard.duration ? { duration: heard.duration } : {}),
|
|
248
|
+
body: heard.speech,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
await rm(dir, { recursive: true, force: true });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/video/url.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type Model } from "@wrenautomation/models";
|
|
2
|
+
import { type Ear } from "@wrenautomation/models/hear";
|
|
3
|
+
export declare const COHERE_VISION_MODEL = "command-a-vision-07-2025";
|
|
4
|
+
/** The text model for asks that need no picture (`video look --with cohere`). */
|
|
5
|
+
export declare const COHERE_TEXT_MODEL = "command-a-03-2025";
|
|
6
|
+
export interface Frame {
|
|
7
|
+
/** Seconds into the video, from the ffmpeg frame grid. */
|
|
8
|
+
at: number;
|
|
9
|
+
text: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** PySceneDetect's detectors: `hash` is the one films trust, `adaptive` the second signal. */
|
|
12
|
+
export declare const SCENE_DETECTORS: readonly ["hash", "adaptive"];
|
|
13
|
+
export type SceneDetector = (typeof SCENE_DETECTORS)[number];
|
|
14
|
+
export interface Watched {
|
|
15
|
+
durationS: number;
|
|
16
|
+
/** Scene cuts ffmpeg found, in seconds. */
|
|
17
|
+
cuts: number[];
|
|
18
|
+
/** PySceneDetect's cuts by detector; only with `sceneDetect`. */
|
|
19
|
+
cutsBy?: Record<SceneDetector, number[]>;
|
|
20
|
+
/** Whether the file has a sound track at all. */
|
|
21
|
+
audio: boolean;
|
|
22
|
+
/** What the ear heard; "" when nobody speaks or speech was not asked for. */
|
|
23
|
+
speech: string;
|
|
24
|
+
frames: Frame[];
|
|
25
|
+
/** The video model's read of the whole screen, when one was given and it answered. */
|
|
26
|
+
whole?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface WatchOptions {
|
|
29
|
+
/** Reads the frames, `BATCH` images a call (`cohereEye`). */
|
|
30
|
+
eye: Model;
|
|
31
|
+
/** Hears the speech (`cohereEar` from models). None: no transcript. */
|
|
32
|
+
ear?: Ear;
|
|
33
|
+
/** Optional: reads the whole video for what it shows (`geminiVideo`). It must take video. */
|
|
34
|
+
video?: Model | null;
|
|
35
|
+
ffmpeg?: string;
|
|
36
|
+
/** The PySceneDetect command (split on spaces: `uvx scenedetect`); set, its cuts come too (`cutsBy`). */
|
|
37
|
+
sceneDetect?: string;
|
|
38
|
+
/** False: skip the transcription (the caller has the words already). */
|
|
39
|
+
speech?: boolean;
|
|
40
|
+
/** ISO 639-1 code of the spoken language. */
|
|
41
|
+
language?: string;
|
|
42
|
+
/** Seconds between frames; stretched so a long video stays under `MAX_FRAMES`. */
|
|
43
|
+
everyS?: number;
|
|
44
|
+
}
|
|
45
|
+
/** Cohere's vision model on a key ring, waiting out a busy ring. */
|
|
46
|
+
export declare const cohereEye: (keys: readonly string[], o?: {
|
|
47
|
+
fetch?: typeof fetch;
|
|
48
|
+
}) => Model;
|
|
49
|
+
/** Cohere's Transcribe on a key ring, waiting out a busy ring. */
|
|
50
|
+
export declare const cohereSpeech: (keys: readonly string[], o?: {
|
|
51
|
+
fetch?: typeof fetch;
|
|
52
|
+
}) => Ear;
|
|
53
|
+
/** `0:03.2`, a mark with a tenth of a second. */
|
|
54
|
+
export declare function stamp(s: number): string;
|
|
55
|
+
type Shot = {
|
|
56
|
+
at: number;
|
|
57
|
+
jpeg: Buffer;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* On-screen text for each frame, `BATCH` frames a call, `BATCHES_AT_ONCE` calls at a time. A
|
|
61
|
+
* batch that cannot be asked at all leaves its frames unread (and a warning); all of them
|
|
62
|
+
* failing throws.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readScreen(shots: Shot[], eye: Model): Promise<Frame[]>;
|
|
65
|
+
/** Cut times from a `list-scenes` CSV (`--skip-cuts`: the header, then one row a scene). Each scene after the first starts on a cut. */
|
|
66
|
+
export declare function parseScenes(csv: string): number[];
|
|
67
|
+
/** PySceneDetect's cuts by one detector. ffmpeg's `scene` misses a fade and a card swap on black; `hash` finds them (designs/studies/2026-10-25-launch-films.md). */
|
|
68
|
+
export declare function sceneDetectCuts(file: string, detector: SceneDetector, cmd: string, dir: string): Promise<number[]>;
|
|
69
|
+
/** Shot changes: the frames `select` keeps are logged by `showinfo`, on stderr. */
|
|
70
|
+
export declare function sceneCuts(file: string, ffmpeg: string): Promise<number[]>;
|
|
71
|
+
/** Watch a video file on disk (a URL is fetched first by `watch` in `url.ts`). */
|
|
72
|
+
export declare function watchFile(input: string, opts: WatchOptions): Promise<Watched>;
|
|
73
|
+
/** The frames with something to say, each under its ffmpeg frame time; the unread ones say so. */
|
|
74
|
+
export declare function onScreenLines(w: Pick<Watched, "frames">): string[];
|
|
75
|
+
export declare const cutsLine: (w: Pick<Watched, "cuts">) => string;
|
|
76
|
+
/** `read`, `unread`: how many frames the model returned a line for. */
|
|
77
|
+
export declare function frameCounts(w: Pick<Watched, "frames">): {
|
|
78
|
+
read: number;
|
|
79
|
+
unread: number;
|
|
80
|
+
};
|
|
81
|
+
/** The `## Speech`, `## On screen` and `## Cuts` sections; the CLI prints it and a SOP source holds it. */
|
|
82
|
+
/** What a film's facts are read from later: the length, the sound track, every detector's cuts. */
|
|
83
|
+
export interface WatchFacts {
|
|
84
|
+
duration_s: number;
|
|
85
|
+
audio: boolean;
|
|
86
|
+
cuts_by: {
|
|
87
|
+
ffmpeg: number[];
|
|
88
|
+
} & Partial<Record<SceneDetector, number[]>>;
|
|
89
|
+
}
|
|
90
|
+
export declare const watchFacts: (w: Pick<Watched, "durationS" | "audio" | "cuts" | "cutsBy">) => WatchFacts;
|
|
91
|
+
/** The facts fence of a rendered page, when it has one. */
|
|
92
|
+
export declare function readWatchFacts(md: string): WatchFacts | null;
|
|
93
|
+
export declare function renderWatched(w: Watched): string;
|
|
94
|
+
export {};
|