commit 1e447c717cf0ea8aca7d50881639451af5f49abe
parent 0c1a529882eb6bdb7037de9f8d1b346a2f019169
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sun, 4 Oct 2026 16:26:19 -0400
editor: fetch-window takes an optional maxHeight; a cached hit reports its height
maxHeight (a whole number of pixels, 144-2160) caps a window fetch's format
selector, and picks a whole-recording fetch's quality: at or under 720 is
video_720, above it original; absent keeps the channel's, else the global,
quality. A cached window answers with its probed height, a cached or
finished whole recording with the height its persist recorded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
10 files changed, 297 insertions(+), 2 deletions(-)
diff --git a/common/lib/clipWindow.test.ts b/common/lib/clipWindow.test.ts
@@ -5,6 +5,7 @@ import {
clipWindowFile,
clipWindowName,
clipWindowSidecar,
+ isFetchMaxHeight,
parseClipProvenance,
parseClipWindowName,
tightestClipWindow,
@@ -142,3 +143,10 @@ test("a saved-video pointer written before `origin` parses unchanged", () => {
assert.ok(bad);
assert.equal(bad?.origin, undefined);
});
+
+test("isFetchMaxHeight: a whole number of pixels from 144 to 2160, nothing else", () => {
+ for (const ok of [144, 360, 720, 1080, 2160]) assert.ok(isFetchMaxHeight(ok), String(ok));
+ for (const bad of [143, 2161, 720.5, 0, -720, Number.NaN, Infinity, "720", null, undefined]) {
+ assert.ok(!isFetchMaxHeight(bad), String(bad));
+ }
+});
diff --git a/common/lib/clipWindow.ts b/common/lib/clipWindow.ts
@@ -52,6 +52,22 @@ export const WIN_EPS = 0.02;
// constant there is a build error, not a lint.
export const MAX_CLIP_WINDOW_SECONDS = 900;
+// The source heights a fetch may cap itself at (`maxHeight` on the fetch-window
+// request, fetch_clip and fetch-via-editor). 144 is the lowest rung a source
+// offers; 2160 is 4K. A whole number in between, or the request is refused —
+// it lands in a yt-dlp `-f` selector, so it is checked, not coerced.
+export const MIN_FETCH_MAX_HEIGHT = 144;
+export const MAX_FETCH_MAX_HEIGHT = 2160;
+
+export function isFetchMaxHeight(v: unknown): v is number {
+ return (
+ typeof v === "number" &&
+ Number.isInteger(v) &&
+ v >= MIN_FETCH_MAX_HEIGHT &&
+ v <= MAX_FETCH_MAX_HEIGHT
+ );
+}
+
export function clipsDirFor(videoDir: string): string {
return path.join(videoDir, CLIPS_DIR_NAME);
}
diff --git a/common/ytdlp/downloadFormat.test.ts b/common/ytdlp/downloadFormat.test.ts
@@ -12,6 +12,7 @@ import {
resolveDownloadFormatSelector,
resolveSourceVideoQuality,
sourceVideoFormatSelector,
+ sourceVideoQualityForMaxHeight,
} from "./downloadFormat";
import { clipFormatSelector as reexported } from "./fetchWindowManaged";
@@ -92,3 +93,12 @@ test("resolveSourceVideoQuality: override beats channel beats global; original w
assert.ok(isSourceVideoQuality("video_720"));
assert.ok(!isSourceVideoQuality("bestvideo_audio"));
});
+
+test("sourceVideoQualityForMaxHeight: a cap at or under 720 is video_720, above it original", () => {
+ assert.equal(sourceVideoQualityForMaxHeight(144), "video_720");
+ assert.equal(sourceVideoQualityForMaxHeight(480), "video_720");
+ assert.equal(sourceVideoQualityForMaxHeight(720), "video_720");
+ assert.equal(sourceVideoQualityForMaxHeight(721), "original");
+ assert.equal(sourceVideoQualityForMaxHeight(1080), "original");
+ assert.equal(sourceVideoQualityForMaxHeight(2160), "original");
+});
diff --git a/common/ytdlp/downloadFormat.ts b/common/ytdlp/downloadFormat.ts
@@ -198,3 +198,14 @@ export function resolveSourceVideoQuality(opts: {
}
return DEFAULT_SOURCE_VIDEO_QUALITY;
}
+
+// The source-video quality a whole-recording fetch that names a height cap
+// asks for. At or under 720 that is "video_720" (≤720p H.264); above it,
+// "original" — the caller said in so many words that 720 is not enough, so the
+// channel's own "video_720" must not quietly overrule it. No cap at all is not
+// handled here: that fetch inherits the channel's, else the global, quality.
+export function sourceVideoQualityForMaxHeight(
+ maxHeight: number,
+): SourceVideoQuality {
+ return maxHeight <= VIDEO_720_MAX_HEIGHT ? VIDEO_720_PRESET : "original";
+}
diff --git a/common/ytdlp/ffprobeDuration.test.ts b/common/ytdlp/ffprobeDuration.test.ts
@@ -0,0 +1,46 @@
+import { test } from "node:test";
+import assert from "node:assert/strict";
+import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
+import { probeVideoHeight } from "./ffprobeDuration";
+
+// Run with:
+// pnpm --filter yt-dlp-transcript-common exec tsx --test ytdlp/ffprobeDuration.test.ts
+//
+// A stand-in ffprobe: a shell script that prints what the test says, so no
+// media file and no real ffprobe are needed.
+async function fakeFfprobe(dir: string, body: string): Promise<string> {
+ const bin = path.join(dir, "ffprobe");
+ await writeFile(bin, `#!/bin/sh\n${body}\n`);
+ await chmod(bin, 0o755);
+ return bin;
+}
+
+test("probeVideoHeight reads the first video stream's height", async () => {
+ const dir = await mkdtemp(path.join(os.tmpdir(), "probe-height-"));
+ try {
+ const ok = await fakeFfprobe(dir, 'echo 720');
+ assert.equal(await probeVideoHeight({ ffprobeBin: ok, file: "x.mp4" }), 720);
+ } finally {
+ await rm(dir, { recursive: true, force: true });
+ }
+});
+
+test("probeVideoHeight is null when ffprobe fails, prints nothing, or is missing", async () => {
+ const dir = await mkdtemp(path.join(os.tmpdir(), "probe-height-"));
+ try {
+ const fails = await fakeFfprobe(dir, "exit 1");
+ assert.equal(await probeVideoHeight({ ffprobeBin: fails, file: "x.mp4" }), null);
+ // An audio-only file: no v:0 stream, so ffprobe prints nothing.
+ await rm(fails);
+ const empty = await fakeFfprobe(dir, "exit 0");
+ assert.equal(await probeVideoHeight({ ffprobeBin: empty, file: "x.m4a" }), null);
+ assert.equal(
+ await probeVideoHeight({ ffprobeBin: path.join(dir, "no-such-bin"), file: "x.mp4" }),
+ null,
+ );
+ } finally {
+ await rm(dir, { recursive: true, force: true });
+ }
+});
diff --git a/common/ytdlp/ffprobeDuration.ts b/common/ytdlp/ffprobeDuration.ts
@@ -46,3 +46,37 @@ export async function probeMediaDurationSec(
return null;
}
}
+
+// The height, in pixels, of a media file's first video stream, or null when
+// ffprobe is missing, errors, times out or the file has no video. Reads the
+// container header only, so it is cheap enough to answer "how tall is the file
+// already on disk" on a cache hit; `timeoutMs` bounds it for a caller that
+// must answer promptly (default 5 s).
+export async function probeVideoHeight(opts: {
+ ffprobeBin: string;
+ file: string;
+ timeoutMs?: number;
+}): Promise<number | null> {
+ try {
+ const result = await execa(
+ opts.ffprobeBin,
+ [
+ "-v",
+ "error",
+ "-select_streams",
+ "v:0",
+ "-show_entries",
+ "stream=height",
+ "-of",
+ "default=noprint_wrappers=1:nokey=1",
+ opts.file,
+ ],
+ { reject: false, timeout: opts.timeoutMs ?? 5000 },
+ );
+ if (result.exitCode !== 0) return null;
+ const h = Number.parseInt(String(result.stdout).trim(), 10);
+ return Number.isInteger(h) && h > 0 ? h : null;
+ } catch {
+ return null;
+ }
+}
diff --git a/editor/app/api/media/fetch-window/[jobId]/route.ts b/editor/app/api/media/fetch-window/[jobId]/route.ts
@@ -77,6 +77,12 @@ export async function GET(
} else {
const pointer = await loadSavedVideo(videoDir);
file = pointer ? savedVideoPath(pointer) : null;
+ // How tall the saved file is, as the persist recorded it: a 720p
+ // persist can fall through to its last-resort rung, and the caller
+ // asked with a height in mind.
+ if (pointer?.format?.height !== undefined) {
+ out.height = pointer.format.height;
+ }
}
if (file) {
const st = await stat(file).catch(() => null);
diff --git a/editor/app/api/media/fetch-window/route.test.ts b/editor/app/api/media/fetch-window/route.test.ts
@@ -0,0 +1,99 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
+
+// Run with:
+// pnpm -C editor exec tsx --test "app/api/media/fetch-window/route.test.ts"
+//
+// The refusals that come before any action runs, and the two cached answers —
+// both read the disk only, so a temp corpus and a stand-in ffprobe are the
+// whole world: no job is queued and nothing is fetched.
+
+const ROOT = await mkdtemp(path.join(os.tmpdir(), "fetch-window-route-"));
+const SLUG = "demo-channel";
+const VIDEO_DIR = path.join(ROOT, "channels", SLUG, "data", "abc123");
+// Set before the route (and getPaths, which caches) is first imported.
+process.env.WORKER_TOKEN = "test-token";
+process.env.TRANSCRIPTS_DIR = ROOT;
+process.env.SETTINGS_FILE = path.join(ROOT, "settings.json");
+process.env.FFPROBE_BIN = path.join(ROOT, "ffprobe");
+await writeFile(process.env.FFPROBE_BIN, "#!/bin/sh\necho 1080\n");
+await chmod(process.env.FFPROBE_BIN, 0o755);
+await mkdir(path.join(VIDEO_DIR, "clips"), { recursive: true });
+await writeFile(
+ path.join(ROOT, "channels", SLUG, "config.json"),
+ JSON.stringify({ name: "Demo", handling: "youtube", url: "https://www.youtube.com/@demo" }),
+);
+const { POST } = await import("./route");
+test.after(() => rm(ROOT, { recursive: true, force: true }));
+
+function post(body: Record<string, unknown>): Promise<Response> {
+ return POST(
+ new Request("http://localhost/api/media/fetch-window", {
+ method: "POST",
+ headers: {
+ authorization: "Bearer test-token",
+ "content-type": "application/json",
+ },
+ body: JSON.stringify(body),
+ }),
+ );
+}
+
+const base = {
+ channelSlug: "demo-channel",
+ videoId: "abc123",
+ requestedBy: "test",
+ from: 10,
+ to: 20,
+};
+
+test("maxHeight out of range, fractional or not a number is refused with 400", async () => {
+ for (const maxHeight of [143, 2161, 720.5, "720", 0, -1]) {
+ for (const full of [false, true]) {
+ const res = await post({ ...base, full, maxHeight });
+ assert.equal(res.status, 400, `${JSON.stringify(maxHeight)} full=${full}`);
+ const body = (await res.json()) as { error: string };
+ assert.match(body.error, /maxHeight must be a whole number of pixels from 144 to 2160/);
+ }
+ }
+});
+
+test("a cached window answers with its probed height", async () => {
+ await writeFile(path.join(VIDEO_DIR, "clips", "5.00-25.00.mp4"), "not really an mp4");
+ const res = await post({ ...base, maxHeight: 720 });
+ assert.equal(res.status, 200);
+ const body = (await res.json()) as Record<string, unknown>;
+ assert.equal(body.cached, true);
+ assert.equal(body.from, 5);
+ assert.equal(body.to, 25);
+ // Taller than the cap asked for: the answer says so, it does not refetch.
+ assert.equal(body.height, 1080);
+});
+
+test("a cached whole recording answers with the height its persist recorded", async () => {
+ const store = path.join(ROOT, "saved-videos", SLUG, "abc123");
+ await mkdir(store, { recursive: true });
+ await writeFile(path.join(store, "abc123.mp4"), "x");
+ const pointer = (format?: unknown) =>
+ writeFile(
+ path.join(VIDEO_DIR, "saved-video.json"),
+ JSON.stringify({ storedAt: "", dir: store, file: "abc123.mp4", bytes: 1, format }),
+ );
+
+ await pointer({ preset: "original", height: 1080, vcodec: "avc1" });
+ let res = await post({ ...base, full: true, maxHeight: 720 });
+ assert.equal(res.status, 200);
+ let body = (await res.json()) as Record<string, unknown>;
+ assert.equal(body.file, path.join(store, "abc123.mp4"));
+ assert.equal(body.height, 1080);
+
+ // A pointer from before formats were recorded: no height, rather than a guess.
+ await pointer(undefined);
+ res = await post({ ...base, full: true });
+ body = (await res.json()) as Record<string, unknown>;
+ assert.equal(res.status, 200);
+ assert.equal("height" in body, false);
+});
diff --git a/editor/app/api/media/fetch-window/route.ts b/editor/app/api/media/fetch-window/route.ts
@@ -1,7 +1,13 @@
import { NextResponse } from "next/server";
import { authorizeWorkerRequest } from "yt-dlp-transcript-common/lib/workerToken";
import { isValidChannelSlug } from "yt-dlp-transcript-common/controller/channels";
-import { MAX_CLIP_WINDOW_SECONDS } from "yt-dlp-transcript-common/lib/clipWindow";
+import {
+ MAX_CLIP_WINDOW_SECONDS,
+ MAX_FETCH_MAX_HEIGHT,
+ MIN_FETCH_MAX_HEIGHT,
+ isFetchMaxHeight,
+} from "yt-dlp-transcript-common/lib/clipWindow";
+import { sourceVideoQualityForMaxHeight } from "yt-dlp-transcript-common/ytdlp/downloadFormat";
import {
fetchFullSourceAction,
fetchWindowAction,
@@ -47,6 +53,7 @@ type Body = {
reason?: unknown;
pad?: unknown;
full?: unknown;
+ maxHeight?: unknown;
};
const str = (v: unknown): string | undefined =>
@@ -77,6 +84,7 @@ function answer(outcome: FetchMediaOutcome): NextResponse {
to: outcome.to,
bytes: outcome.bytes,
provenance: outcome.provenance,
+ ...(outcome.height !== undefined ? { height: outcome.height } : {}),
},
{ status: 200 },
);
@@ -140,12 +148,37 @@ export async function POST(request: Request) {
requestedAt: new Date().toISOString(),
};
+ // THE HEIGHT CAP, optional. Absent (or null) keeps every default as it was;
+ // anything else must be a whole number of pixels in range, because it ends up
+ // inside a yt-dlp `-f` selector.
+ const maxHeight =
+ body.maxHeight === undefined || body.maxHeight === null
+ ? undefined
+ : body.maxHeight;
+ if (maxHeight !== undefined && !isFetchMaxHeight(maxHeight)) {
+ return NextResponse.json(
+ {
+ error:
+ `maxHeight must be a whole number of pixels from ` +
+ `${MIN_FETCH_MAX_HEIGHT} to ${MAX_FETCH_MAX_HEIGHT}`,
+ },
+ { status: 400 },
+ );
+ }
+
if (body.full === true) {
+ // A cap at or under 720 asks for "video_720"; one above it asks for
+ // "original" in so many words. No cap: the channel's, else the global,
+ // source-video quality, as a persist from the video page would.
return answer(
await fetchFullSourceAction({
slug: channelSlug,
videoId,
provenance,
+ quality:
+ maxHeight === undefined
+ ? undefined
+ : sourceVideoQualityForMaxHeight(maxHeight),
}),
);
}
@@ -184,6 +217,7 @@ export async function POST(request: Request) {
from,
to,
provenance,
+ maxHeight,
}),
);
}
diff --git a/editor/app/channels/[slug]/videos/[id]/videoActions.ts b/editor/app/channels/[slug]/videos/[id]/videoActions.ts
@@ -64,6 +64,7 @@ import {
} from "yt-dlp-transcript-common/lib/savedVideo";
import {
MAX_CLIP_WINDOW_SECONDS,
+ isFetchMaxHeight,
type ClipProvenance,
} from "yt-dlp-transcript-common/lib/clipWindow";
import {
@@ -74,6 +75,7 @@ import {
fetchWindowManaged,
type FetchWindowProvenance,
} from "yt-dlp-transcript-common/ytdlp/fetchWindowManaged";
+import { probeVideoHeight } from "yt-dlp-transcript-common/ytdlp/ffprobeDuration";
import { detectPlatform } from "yt-dlp-transcript-common/lib/platform";
import { applyTagAssignmentsAction } from "../../../../tags/actions";
import {
@@ -916,7 +918,11 @@ function requesterLine(o: SavedVideoOrigin | FetchWindowProvenance): string {
}
export type FetchMediaOutcome =
- // Already on disk. No job, no bytes, no politeness owed.
+ // Already on disk. No job, no bytes, no politeness owed. `height` is how
+ // tall the file on disk is, when that is known (a saved pointer's recorded
+ // format, a window's probed stream) — a cached file was fetched for an
+ // earlier ask, so it may be taller than this one's cap, and the caller is
+ // the one who can tell whether that matters.
| {
ok: true;
cached: true;
@@ -925,6 +931,7 @@ export type FetchMediaOutcome =
to: number;
bytes: number;
provenance: ClipProvenance | SavedVideoOrigin | null;
+ height?: number;
}
// Queued. `file` is where the window WILL be; for a full source it is not
// knowable until the container's real extension is (null until then).
@@ -981,6 +988,10 @@ export async function fetchWindowAction(req: {
to: number;
provenance: FetchWindowProvenance;
queueKey?: string;
+ // The source height to cap the fetch at (clipFormatSelector's), absent for
+ // the default (DEFAULT_CLIP_MAX_HEIGHT). Checked again here for the same
+ // reason the window is: a replayed spec is a file on disk.
+ maxHeight?: number;
// Hand back the StreamActionResult instead of detaching it. Set by Retry,
// which renders the log; the HTTP route leaves it off and polls.
stream?: boolean;
@@ -1005,6 +1016,13 @@ export async function fetchWindowAction(req: {
`(at most ${MAX_CLIP_WINDOW_SECONDS}s, from < to, from >= 0).`,
};
}
+ if (req.maxHeight !== undefined && !isFetchMaxHeight(req.maxHeight)) {
+ return {
+ ok: false,
+ status: 400,
+ error: `maxHeight ${req.maxHeight} is not a source height to cap a fetch at.`,
+ };
+ }
const r = await loadConfigOrError(slug);
if (!r.ok) return { ok: false, status: 404, error: r.error };
const paths = getPaths();
@@ -1015,6 +1033,13 @@ export async function fetchWindowAction(req: {
// neighbour it already covers rather than being downloaded again.
const hit = await findContainingClipWindow(videoDir, from, to);
if (hit) {
+ // The window's own height, read off its header: a window holds seconds,
+ // so the probe is quick, and it is the only record of how tall a window
+ // fetched under an earlier cap is.
+ const height = await probeVideoHeight({
+ ffprobeBin: paths.ffprobeBin,
+ file: hit.path,
+ });
return {
ok: true,
cached: true,
@@ -1023,6 +1048,7 @@ export async function fetchWindowAction(req: {
to: hit.to,
bytes: hit.bytes,
provenance: hit.provenance,
+ ...(height !== null ? { height } : {}),
};
}
@@ -1086,6 +1112,7 @@ export async function fetchWindowAction(req: {
to,
webpageUrl: url,
queueKey: req.queueKey,
+ ...(req.maxHeight !== undefined ? { maxHeight: req.maxHeight } : {}),
...req.provenance,
},
},
@@ -1103,6 +1130,7 @@ export async function fetchWindowAction(req: {
from,
to,
provenance: req.provenance,
+ maxHeight: req.maxHeight,
cookiePolicy: resolveCookiePolicy(settings, r.config),
onLog,
signal,
@@ -1188,6 +1216,9 @@ export async function fetchFullSourceAction(req: {
to: 0,
bytes: pointer.bytes,
provenance: pointer.origin ?? null,
+ ...(pointer.format?.height !== undefined
+ ? { height: pointer.format.height }
+ : {}),
};
}