@wrenautomation/read 0.0.0-stage → 0.30.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.
Files changed (72) hide show
  1. package/LICENSE.md +61 -0
  2. package/README.md +36 -2
  3. package/dist/fetch/fetcher.d.ts +84 -0
  4. package/dist/fetch/fetcher.js +180 -0
  5. package/dist/fetch/fetcher.js.map +1 -0
  6. package/dist/fetch/index.d.ts +9 -0
  7. package/dist/fetch/index.js +10 -0
  8. package/dist/fetch/index.js.map +1 -0
  9. package/dist/fetch/ladder.d.ts +44 -0
  10. package/dist/fetch/ladder.js +18 -0
  11. package/dist/fetch/ladder.js.map +1 -0
  12. package/dist/fetch/robots.d.ts +14 -0
  13. package/dist/fetch/robots.js +92 -0
  14. package/dist/fetch/robots.js.map +1 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.js +12 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/study/claims.d.ts +19 -0
  19. package/dist/study/claims.js +13 -0
  20. package/dist/study/claims.js.map +1 -0
  21. package/dist/study/drafts.d.ts +43 -0
  22. package/dist/study/drafts.js +93 -0
  23. package/dist/study/drafts.js.map +1 -0
  24. package/dist/study/evidence.d.ts +17 -0
  25. package/dist/study/evidence.js +72 -0
  26. package/dist/study/evidence.js.map +1 -0
  27. package/dist/study/grounding.d.ts +29 -0
  28. package/dist/study/grounding.js +58 -0
  29. package/dist/study/grounding.js.map +1 -0
  30. package/dist/study/index.d.ts +11 -0
  31. package/dist/study/index.js +12 -0
  32. package/dist/study/index.js.map +1 -0
  33. package/dist/study/report.d.ts +55 -0
  34. package/dist/study/report.js +56 -0
  35. package/dist/study/report.js.map +1 -0
  36. package/dist/text/entities.d.ts +2 -0
  37. package/dist/text/entities.js +4 -0
  38. package/dist/text/entities.js.map +1 -0
  39. package/dist/text/htmltext.d.ts +30 -0
  40. package/dist/text/htmltext.js +208 -0
  41. package/dist/text/htmltext.js.map +1 -0
  42. package/dist/text/index.d.ts +7 -0
  43. package/dist/text/index.js +8 -0
  44. package/dist/text/index.js.map +1 -0
  45. package/dist/text/text.d.ts +8 -0
  46. package/dist/text/text.js +38 -0
  47. package/dist/text/text.js.map +1 -0
  48. package/dist/video/gemini.d.ts +46 -0
  49. package/dist/video/gemini.js +93 -0
  50. package/dist/video/gemini.js.map +1 -0
  51. package/dist/video/index.d.ts +39 -0
  52. package/dist/video/index.js +16 -0
  53. package/dist/video/index.js.map +1 -0
  54. package/dist/video/list.d.ts +35 -0
  55. package/dist/video/list.js +32 -0
  56. package/dist/video/list.js.map +1 -0
  57. package/dist/video/read.d.ts +53 -0
  58. package/dist/video/read.js +92 -0
  59. package/dist/video/read.js.map +1 -0
  60. package/dist/video/shots.d.ts +35 -0
  61. package/dist/video/shots.js +139 -0
  62. package/dist/video/shots.js.map +1 -0
  63. package/dist/video/tools.d.ts +16 -0
  64. package/dist/video/tools.js +29 -0
  65. package/dist/video/tools.js.map +1 -0
  66. package/dist/video/url.d.ts +73 -0
  67. package/dist/video/url.js +255 -0
  68. package/dist/video/url.js.map +1 -0
  69. package/dist/video/watch.d.ts +94 -0
  70. package/dist/video/watch.js +321 -0
  71. package/dist/video/watch.js.map +1 -0
  72. package/package.json +55 -3
@@ -0,0 +1,92 @@
1
+ /**
2
+ * A video or podcast read as one markdown document: the `VideoRead` shape, the captions renderer
3
+ * (a YouTube json3 track to a heading per chapter) and the audio hearer (a file cut by ffmpeg into
4
+ * clips, each transcribed by a model that takes audio). Everything here takes text or a file on
5
+ * disk; fetching the captions or the audio is `url.ts`'s. Every
6
+ * caller passes its own models and tools. Nothing here touches a database.
7
+ */
8
+ import { readdir, readFile } from "node:fs/promises";
9
+ import { dirname, join } from "node:path";
10
+ import { readMedia } from "./gemini.js";
11
+ import { clock, run } from "./tools.js";
12
+ const MARK_EVERY_S = 60;
13
+ /** Seconds of video per model call. */
14
+ export const CLIP_S = 600;
15
+ /** yt-dlp's `YYYYMMDD` as an ISO date. */
16
+ export const isoDate = (d) => d?.replace(/^(\d{4})(\d{2})(\d{2})$/, "$1-$2-$3");
17
+ /** A model's "none": nothing shown or said. */
18
+ export const real = (s) => s && s.toLowerCase() !== "none";
19
+ /** Captions as markdown: a heading per chapter, a `[m:ss]` marker every minute so a reader can cite a moment. */
20
+ export function captionsBody(info, track) {
21
+ const chapters = info.chapters?.length
22
+ ? info.chapters
23
+ : [{ start_time: 0, end_time: info.duration ?? Number.POSITIVE_INFINITY, title: info.title }];
24
+ const words = track.events
25
+ .filter((e) => e.segs?.length)
26
+ .map((e) => ({
27
+ t: e.tStartMs / 1000,
28
+ text: (e.segs ?? [])
29
+ .map((s) => s.utf8)
30
+ .join("")
31
+ .replace(/\s+/g, " ")
32
+ .trim(),
33
+ }))
34
+ .filter((w) => w.text);
35
+ return chapters
36
+ .map((c) => {
37
+ const lines = [];
38
+ let mark = Number.NEGATIVE_INFINITY;
39
+ for (const w of words) {
40
+ if (w.t < c.start_time || w.t >= c.end_time)
41
+ continue;
42
+ if (w.t - mark >= MARK_EVERY_S) {
43
+ mark = w.t;
44
+ lines.push(`\n[${clock(w.t)}]`);
45
+ }
46
+ lines.push(w.text);
47
+ }
48
+ return `## [${clock(c.start_time)}] ${c.title}\n${lines.join(" ").trim()}\n`;
49
+ })
50
+ .join("\n");
51
+ }
52
+ const HEARD = (start) => `Transcribe the speech in this podcast audio: every word spoken, in paragraphs, each starting with its time in the episode as [m:ss], or [h:mm:ss] past the hour (this clip starts at ${clock(start)}), and the speaker's name in bold when it's known. Fix only obvious mishearings. Leave out ad reads. Markdown, no heading. If nobody speaks, reply with the single word none.`;
53
+ /**
54
+ * A podcast episode heard from its audio file on disk: cut by ffmpeg into `CLIP_S` mono clips
55
+ * small enough to send inline (about 2 MB each), each transcribed by `model` (`geminiVideo`), 4 at
56
+ * a time, times running on across clips. The clips are written beside the file and left for the
57
+ * caller's cleanup. `label` names the calls in errors.
58
+ */
59
+ export async function hearAudio(file, opts) {
60
+ if (!opts.model.takes.has("audio"))
61
+ throw new Error(`${file}: ${opts.model.id} takes no audio`);
62
+ const clipS = opts.clipS ?? CLIP_S;
63
+ const ffmpeg = opts.ffmpeg ?? "ffmpeg";
64
+ const label = opts.label ?? file;
65
+ const dir = dirname(file);
66
+ await run(ffmpeg, [
67
+ ...["-hide_banner", "-loglevel", "error", "-i", file, "-vn", "-ac", "1", "-ar", "16000"],
68
+ ...["-c:a", "libopus", "-b:a", "24k", "-f", "segment", "-segment_time", String(clipS)],
69
+ ...["-reset_timestamps", "1", join(dir, "clip%03d.ogg")],
70
+ ], { maxBuffer: 16 * 1024 * 1024 });
71
+ const clips = (await readdir(dir)).filter((f) => f.startsWith("clip")).sort();
72
+ if (!clips.length)
73
+ throw new Error(`${file}: ffmpeg cut no clips`);
74
+ const hear = async (clip, i) => {
75
+ const at = `${label} ${clock(i * clipS)} speech`;
76
+ const said = await readMedia(opts.model, [{ type: "audio", data: await readFile(join(dir, clip)), mediaType: "audio/ogg" }], HEARD(i * clipS)).catch((err) => {
77
+ throw new Error(`${at}: ${err instanceof Error ? err.message : err}`, { cause: err });
78
+ });
79
+ return real(said) ? said : "";
80
+ };
81
+ const speech = [];
82
+ for (let i = 0; i < clips.length; i += 4)
83
+ speech.push(...(await Promise.all(clips.slice(i, i + 4).map((f, j) => hear(f, i + j)))));
84
+ const { stdout } = await run(ffmpeg.replace(/ffmpeg$/, "ffprobe"), [
85
+ ...["-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", file],
86
+ ]).catch(() => ({ stdout: "" }));
87
+ return {
88
+ speech: `## Speech\n\n${speech.filter(Boolean).join("\n\n") || "(no speech)"}\n`,
89
+ duration: Math.round(Number(stdout.trim()) || 0),
90
+ };
91
+ }
92
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/video/read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { type Model } from "@wrenautomation/models";
2
+ /** The labels asked, each with its closed set. */
3
+ export declare const LABELS: readonly ["type", "move", "in"];
4
+ export type Label = (typeof LABELS)[number];
5
+ export type LabelSets = Record<Label, readonly string[]>;
6
+ export type ShotLabels = Record<Label, string | null>;
7
+ /** A shot to label: its start and length in seconds. */
8
+ export interface ShotSpan {
9
+ t: number;
10
+ len: number;
11
+ }
12
+ export declare const labelAsk: (sets: LabelSets, first: boolean) => string;
13
+ /** The model's answer, held to the sets: an answer outside one is null. */
14
+ export declare function parseLabels(answer: string, sets: LabelSets): ShotLabels;
15
+ /** The frame times for a shot: just before it (not for the first), its start, middle and end. */
16
+ export declare function shotTimes(s: ShotSpan): number[];
17
+ /**
18
+ * Each shot's labels, one vision call a shot, `AT_ONCE` at a time. A shot whose call fails keeps
19
+ * null labels and a warning; every call failing throws.
20
+ */
21
+ export declare function labelShots(file: string, shots: readonly ShotSpan[], sets: LabelSets, o: {
22
+ /** Takes images (`cohereEye`). */
23
+ eye: Model;
24
+ ffmpeg?: string;
25
+ bright?: boolean;
26
+ /** For tests: stands in for the wait on a per-minute cap. */
27
+ pause?: (ms: number) => Promise<void>;
28
+ }): Promise<ShotLabels[]>;
29
+ /** Each label's share of hand labels the machine matched; a hand label that is null is skipped. */
30
+ export declare function labelMatch(hand: readonly Partial<ShotLabels>[], machine: readonly ShotLabels[]): Record<Label, {
31
+ n: number;
32
+ hits: number;
33
+ share: number;
34
+ misses: [string, string | null][];
35
+ }>;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Shot labels by vision (designs/2026-10-25-competitor-analyzer.md, Read): for each shot of a film,
3
+ * frames at its start, middle and end, and one just before it starts, go to a vision model
4
+ * (`cohereEye`), which picks the shot's type, camera move and way in from closed sets the caller gives.
5
+ * An answer outside a set is null, never guessed. The caller decides which labels it trusts: a
6
+ * label ships only once it matches a hand seed at 80% or better (`labelMatch`).
7
+ */
8
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { isTransient } from "@wrenautomation/models";
12
+ import { runFor, TOOL_MS } from "./tools.js";
13
+ /** The labels asked, each with its closed set. */
14
+ export const LABELS = ["type", "move", "in"];
15
+ /** Calls at a time: the vision model's per-minute limit refuses 3 at once on a film. */
16
+ const AT_ONCE = 1;
17
+ /** A per-minute cap with no `retry-after`: wait it out this long, at most `CAP_WAITS` times a shot. */
18
+ const CAP_WAIT_MS = 61_000;
19
+ const CAP_WAITS = 3;
20
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
21
+ /** `fn`, again after the vendor's wait (a minute when it names none) each time it says "later". */
22
+ async function waitOutCap(fn, pause) {
23
+ for (let i = 0;; i++) {
24
+ try {
25
+ return await fn();
26
+ }
27
+ catch (err) {
28
+ if (i >= CAP_WAITS || !isTransient(err))
29
+ throw err;
30
+ const until = err.retryAfter ? Date.parse(err.retryAfter) - Date.now() : Number.NaN;
31
+ await pause(Number.isNaN(until) ? CAP_WAIT_MS : Math.max(0, Math.min(until, CAP_WAIT_MS)));
32
+ }
33
+ }
34
+ }
35
+ /** Dark films read as nothing; this lifts the shadows before the model sees them. */
36
+ const BRIGHTEN = "eq=gamma=1.6:contrast=1.15";
37
+ const MEANS = {
38
+ type: "what the shot shows: ui (an app screen), b-roll (real-world footage), graphic (motion design, no app), logo, text-card (words alone on a plain ground), talking-head (a person speaking to camera)",
39
+ move: "how the camera moves within the shot: static, push (in), pull (out), pan, track (slides along), orbit (circles the subject), roll (tilts the frame or the plane), rack-focus, macro (extreme close-up), ui-zoom (zooms into part of a screen)",
40
+ in: "how the shot begins after the one before: cut, dissolve (the two overlap), fade (from or through black or white), whip (a fast blur)",
41
+ };
42
+ export const labelAsk = (sets, first) => `These frames are from one shot of a produced product film, in order: ${first ? "its start" : "the last moment of the shot before it, then its start"}, its middle and its end. Label the shot.
43
+ ${LABELS.map((l) => `- ${l}: ${MEANS[l]}. One of: ${sets[l].join(", ")}.`).join("\n")}
44
+ Answer JSON only: {${LABELS.map((l) => `"${l}": "..."`).join(", ")}}. Use null for a label you cannot tell.`;
45
+ /** The model's answer, held to the sets: an answer outside one is null. */
46
+ export function parseLabels(answer, sets) {
47
+ const json = /\{[\s\S]*\}/.exec(answer)?.[0];
48
+ let got = {};
49
+ try {
50
+ got = json ? JSON.parse(json) : {};
51
+ }
52
+ catch {
53
+ got = {};
54
+ }
55
+ const pick = (l) => {
56
+ const v = typeof got[l] === "string" ? got[l].trim().toLowerCase() : null;
57
+ return v && sets[l].includes(v) ? v : null;
58
+ };
59
+ return { type: pick("type"), move: pick("move"), in: pick("in") };
60
+ }
61
+ /** The frame times for a shot: just before it (not for the first), its start, middle and end. */
62
+ export function shotTimes(s) {
63
+ const edge = Math.min(0.3, s.len * 0.15);
64
+ const r = (x) => Math.round(x * 100) / 100;
65
+ return [
66
+ ...(s.t > 0 ? [r(Math.max(0, s.t - 0.2))] : []),
67
+ r(s.t + edge),
68
+ r(s.t + s.len / 2),
69
+ r(s.t + s.len - edge),
70
+ ];
71
+ }
72
+ async function frameAt(file, at, out, ffmpeg, bright) {
73
+ const scale = "scale='if(gt(iw,ih),-2,360)':'if(gt(iw,ih),360,-2)'";
74
+ await runFor(ffmpeg, [
75
+ ...["-nostdin", "-hide_banner", "-loglevel", "error", "-ss", String(at), "-i", file, "-an"],
76
+ ...["-frames:v", "1", "-vf", bright ? `${scale},${BRIGHTEN}` : scale, "-q:v", "4", out],
77
+ ], TOOL_MS);
78
+ return readFile(out);
79
+ }
80
+ /**
81
+ * Each shot's labels, one vision call a shot, `AT_ONCE` at a time. A shot whose call fails keeps
82
+ * null labels and a warning; every call failing throws.
83
+ */
84
+ export async function labelShots(file, shots, sets, o) {
85
+ const ffmpeg = o.ffmpeg ?? "ffmpeg";
86
+ const dir = await mkdtemp(join(tmpdir(), "read-shots-"));
87
+ const none = { type: null, move: null, in: null };
88
+ try {
89
+ const out = [];
90
+ let failed = 0;
91
+ for (let i = 0; i < shots.length; i += AT_ONCE) {
92
+ const group = shots.slice(i, i + AT_ONCE);
93
+ const done = await Promise.allSettled(group.map((s, j) => waitOutCap(() => labelOne(s, i + j), o.pause ?? sleep)));
94
+ for (const r of done) {
95
+ if (r.status === "fulfilled")
96
+ out.push(r.value);
97
+ else {
98
+ failed++;
99
+ console.warn(`shot ${out.length + 1}: ${r.reason}; labels left empty`);
100
+ out.push(none);
101
+ }
102
+ }
103
+ }
104
+ if (shots.length && failed === shots.length)
105
+ throw new Error("every shot's label call failed");
106
+ return out;
107
+ }
108
+ finally {
109
+ await rm(dir, { recursive: true, force: true });
110
+ }
111
+ async function labelOne(s, n) {
112
+ const times = shotTimes(s);
113
+ const jpegs = await Promise.all(times.map((at, k) => frameAt(file, at, join(dir, `${n}-${k}.jpg`), ffmpeg, o.bright ?? true)));
114
+ const { text } = await o.eye.complete({
115
+ prompt: [
116
+ { type: "text", text: labelAsk(sets, s.t <= 0) },
117
+ ...jpegs.map((b) => ({ type: "image", data: b, mediaType: "image/jpeg" })),
118
+ ],
119
+ });
120
+ return parseLabels(text, sets);
121
+ }
122
+ }
123
+ /** Each label's share of hand labels the machine matched; a hand label that is null is skipped. */
124
+ export function labelMatch(hand, machine) {
125
+ return Object.fromEntries(LABELS.map((l) => {
126
+ const pairs = hand.flatMap((h, i) => h[l] ? [[h[l], machine[i]?.[l] ?? null]] : []);
127
+ const hits = pairs.filter(([a, b]) => a === b).length;
128
+ return [
129
+ l,
130
+ {
131
+ n: pairs.length,
132
+ hits,
133
+ share: pairs.length ? hits / pairs.length : 0,
134
+ misses: pairs.filter(([a, b]) => a !== b),
135
+ },
136
+ ];
137
+ }));
138
+ }
139
+ //# sourceMappingURL=shots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shots.js","sourceRoot":"","sources":["../../src/video/shots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What the source readers share: a child tool run with a time limit, and the clock mark.
3
+ * Own file so `watch.ts` and any other reader import the same one.
4
+ */
5
+ import { execFile } from "node:child_process";
6
+ export declare const run: typeof execFile.__promisify__;
7
+ /** yt-dlp's metadata call, or a download and remux of a reel, past this is stuck. */
8
+ export declare const TOOL_MS = 300000;
9
+ /** Run a tool; past `ms` it is killed and the error says which tool and how long. */
10
+ export declare function runFor(cmd: string, args: string[], ms: number, opts?: {
11
+ maxBuffer?: number;
12
+ }): Promise<{
13
+ stdout: string;
14
+ stderr: string;
15
+ }>;
16
+ export declare const clock: (s: number) => string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * What the source readers share: a child tool run with a time limit, and the clock mark.
3
+ * Own file so `watch.ts` and any other reader import the same one.
4
+ */
5
+ import { execFile } from "node:child_process";
6
+ import { basename } from "node:path";
7
+ import { promisify } from "node:util";
8
+ export const run = promisify(execFile);
9
+ /** yt-dlp's metadata call, or a download and remux of a reel, past this is stuck. */
10
+ export const TOOL_MS = 300_000;
11
+ /** Run a tool; past `ms` it is killed and the error says which tool and how long. */
12
+ export async function runFor(cmd, args, ms, opts = {}) {
13
+ try {
14
+ return await run(cmd, args, { ...opts, timeout: ms, killSignal: "SIGKILL" });
15
+ }
16
+ catch (err) {
17
+ if (err.killed)
18
+ throw new Error(`${basename(cmd)} timed out after ${Math.round(ms / 1000)}s`);
19
+ throw err;
20
+ }
21
+ }
22
+ export const clock = (s) => {
23
+ const h = Math.floor(s / 3600);
24
+ const m = Math.floor((s % 3600) / 60);
25
+ const sec = Math.floor(s % 60);
26
+ const mm = `${m}:${String(sec).padStart(2, "0")}`;
27
+ return h ? `${h}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}` : mm;
28
+ };
29
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/video/tools.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,CAAC,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEvC,qFAAqF;AACrF,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,GAAW,EACX,IAAc,EACd,EAAU,EACV,OAA+B,EAAE;IAEjC,IAAI,CAAC;QACH,OAAO,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;IAC/E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA4B,CAAC,MAAM;YACtC,MAAM,IAAI,KAAK,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QAChF,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE;IACzC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAClD,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACvF,CAAC,CAAC"}
@@ -0,0 +1,73 @@
1
+ import type { Model } from "@wrenautomation/models";
2
+ import type { VideoInfo } from "./index.js";
3
+ import { type VideoRead } from "./read.js";
4
+ import { type Watched, type WatchOptions } from "./watch.js";
5
+ /** What yt-dlp's `-j` prints for a download; only what the readers use. */
6
+ export interface VideoMeta extends Pick<VideoInfo, "id" | "title" | "channel" | "upload_date" | "webpage_url" | "duration" | "thumbnail"> {
7
+ extractor_key?: string;
8
+ description?: string;
9
+ uploader?: string;
10
+ width?: number;
11
+ height?: number;
12
+ }
13
+ /** yt-dlp's download of a reel or video: 540p on the short side, remuxed to mp4. */
14
+ export declare function download(url: string, ytDlp: string, dir: string): Promise<{
15
+ file: string;
16
+ info: VideoMeta;
17
+ }>;
18
+ /**
19
+ * Watch a video: a URL yt-dlp fetches first (`ytDlp` is the command, split on spaces:
20
+ * `uvx yt-dlp`), or a file on disk, which goes straight to `watchFile`. A URL's yt-dlp
21
+ * metadata comes back as `info`; a duration ffmpeg could not read falls back to it.
22
+ */
23
+ export declare function watch(input: string, opts: WatchOptions & {
24
+ ytDlp?: string;
25
+ }): Promise<Watched & {
26
+ info?: VideoMeta;
27
+ }>;
28
+ /**
29
+ * A YouTube video from its captions and chapters, through `yt-dlp` (`ytDlp` is the command,
30
+ * split on spaces: `uvx yt-dlp`). With a `video` model, an `## On screen` section follows: what
31
+ * the video showed, read from the frames, since the captions carry only the speech.
32
+ */
33
+ export declare function readYoutube(url: string, opts: {
34
+ ytDlp: string;
35
+ video?: Model | null;
36
+ fetchFn?: typeof fetch;
37
+ }): Promise<VideoRead>;
38
+ /**
39
+ * A YouTube video read from its URL alone, no download and no home IP: `model` hears the speech
40
+ * and reads the screen in `CLIP_S` clips (one clip when the length isn't known). The title,
41
+ * channel and thumbnail come from YouTube's oEmbed. For a server; a machine with yt-dlp and a
42
+ * home IP reads the captions instead (`readYoutube`).
43
+ */
44
+ export declare function readYoutubeByUrl(url: string, opts: {
45
+ /** Takes a YouTube URL as video (`geminiVideo`). */
46
+ model: Model;
47
+ fetchFn?: typeof fetch;
48
+ durationS?: number | null | undefined;
49
+ }): Promise<VideoRead>;
50
+ /**
51
+ * Any other video `yt-dlp` fetches (an Instagram reel, TikTok, X). No captions there, so
52
+ * `watch` hears the audio and reads the frames (its `video` model adds a read of the whole
53
+ * screen when given). The body holds the caption, then Speech, On screen and Cuts.
54
+ */
55
+ export declare function readVideo(url: string, opts: WatchOptions & {
56
+ ytDlp: string;
57
+ }): Promise<VideoRead>;
58
+ /**
59
+ * A podcast episode heard from its audio URL: downloaded to a temp file, then
60
+ * `hearAudio` (ffmpeg clips, `model` transcribes each). `url` is the episode's page; `title` and
61
+ * `creator` are what its feed said. The id is a hash of the audio URL.
62
+ */
63
+ export declare function readAudio(audioUrl: string, opts: {
64
+ url: string;
65
+ title?: string | null;
66
+ creator?: string | null;
67
+ /** Takes audio (`geminiVideo`). */
68
+ model: Model;
69
+ fetchFn?: typeof fetch;
70
+ ffmpeg?: string;
71
+ /** Seconds per clip; `CLIP_S` unless a test cuts a short tone. */
72
+ clipS?: number;
73
+ }): Promise<VideoRead>;