Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

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:
Mcommon/lib/clipWindow.test.ts | 8++++++++
Mcommon/lib/clipWindow.ts | 16++++++++++++++++
Mcommon/ytdlp/downloadFormat.test.ts | 10++++++++++
Mcommon/ytdlp/downloadFormat.ts | 11+++++++++++
Acommon/ytdlp/ffprobeDuration.test.ts | 46++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/ytdlp/ffprobeDuration.ts | 34++++++++++++++++++++++++++++++++++
Meditor/app/api/media/fetch-window/[jobId]/route.ts | 6++++++
Aeditor/app/api/media/fetch-window/route.test.ts | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Meditor/app/api/media/fetch-window/route.ts | 36+++++++++++++++++++++++++++++++++++-
Meditor/app/channels/[slug]/videos/[id]/videoActions.ts | 33++++++++++++++++++++++++++++++++-
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 } + : {}), }; }