Archilyzer · Source

archilyzer

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

commit 922071fc240f6564c9665f387b9dea83c2f26ec2
parent 512de151cf395f602530b4bc2434bb3e18f51b24
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sun, 20 Sep 2026 19:23:12 -0400

editor: show a fetched window, and who asked for it

A `clips/` directory that nothing renders is bytes nobody can account for. The
video page gets a "Fetched windows" card — span, duration, size, requester,
manifest/clip, the reason in their own words, and when it landed — modelled on
the persisted-source panel, which answers the same question for a container.

It also says the thing the card exists to stop somebody assuming: a window is
not a download. The fetch writes no metadata.info.json, so the video's pipeline
state and its absence from the index are unchanged.

A saved-video pointer now renders its `origin` as "Requested by". `keepReason`
cannot carry that — it stays override/pin so the retention prune leaves the
container alone — so "why is this 4 GB file here" needs its own line.

On /jobs, "Fetch window" alone is the least useful row on the page: every
window fetch has that kind. jobSpecDetail reads the requester and clip off the
job's own replay spec, so the row says which tool wanted which clip without a
second place to store it. No per-channel count anywhere: that would be a walk
of every video dir to draw one number.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Acommon/jobs/jobDetail.ts | 24++++++++++++++++++++++++
Mcommon/jobs/listJobs.ts | 6++++++
Mcommon/views/jobRowView.ts | 5+++++
Mcommon/views/jobRows.ts | 3+++
Meditor/app/channels/[slug]/videos/[id]/components/VideoPanel.tsx | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Meditor/app/channels/[slug]/videos/[id]/page.tsx | 12++++++++++++
Meditor/app/jobs/components/JobsTable.tsx | 7+++++++
Meditor/app/jobs/components/RunningJobsList.tsx | 6++++++
8 files changed, 171 insertions(+), 0 deletions(-)

diff --git a/common/jobs/jobDetail.ts b/common/jobs/jobDetail.ts @@ -0,0 +1,24 @@ +import type { JobSpec } from "./jobSpec"; + +// The one extra phrase a job row carries beyond its kind. +// +// "Fetch window" on its own is the least useful row on /jobs: every fetch-window +// job has that kind, and the question the operator has is which tool asked and +// for what. The answer is already on the job — the replay spec's params — so +// this reads it rather than adding a second place to store it. +// +// Pure and total: a kind with nothing to add returns undefined and the row +// renders exactly as it did. +export function jobSpecDetail( + kind: string | undefined, + spec: JobSpec | null | undefined, +): string | undefined { + if (kind !== "fetch-window") return undefined; + const p = spec?.params ?? {}; + const s = (v: unknown): string | null => + typeof v === "string" && v.trim() !== "" ? v.trim() : null; + const by = s(p.requestedBy); + if (!by) return undefined; + const what = [s(p.manifest), s(p.clipId)].filter(Boolean).join("/"); + return what ? `${by} · ${what}` : by; +} diff --git a/common/jobs/listJobs.ts b/common/jobs/listJobs.ts @@ -7,6 +7,7 @@ import { getRegistry, type JobRecord, type JobStatus } from "./registry"; // One page of job rows, each a couple of small sidecar reads. const ENTRY_READ_CONCURRENCY = 32; import { metaPath, readJobMeta } from "./jobMeta"; +import { jobSpecDetail } from "./jobDetail"; import { jobIdTime } from "./ulid"; export type JobListEntry = { @@ -26,6 +27,9 @@ export type JobListEntry = { replayable: boolean; logPath: string; logSize: number; + // A short phrase naming what this particular job is for, when its kind alone + // does not say (a fetch-window job's requester and clip). See jobSpecDetail. + detail?: string; }; export type JobsPage = { @@ -104,6 +108,7 @@ async function buildEntry( exitCode: live_.exitCode, inRegistry: true, replayable: Boolean(live_.spec), + detail: jobSpecDetail(live_.kind, live_.spec), logPath, logSize, }; @@ -129,6 +134,7 @@ async function buildEntry( exitCode: meta.exitCode, inRegistry: false, replayable: Boolean(meta.spec), + detail: jobSpecDetail(meta.kind, meta.spec), logPath, logSize, }; diff --git a/common/views/jobRowView.ts b/common/views/jobRowView.ts @@ -89,6 +89,11 @@ export type JobRowView = { stuck?: { reason: StuckReason; heldMs: number }; pid?: number; lastLogLine?: string; // stuck rows only — see readLastLogLine + // A short phrase naming what THIS job is for, when the kind alone does not + // say it — "umtool · elfpire-eva/c03" on a fetch-window row. Built by + // jobs/jobDetail.ts from the job's own replay spec; absent for every kind + // that has nothing to add, so no existing row changes. + detail?: string; // Which adapter built it. Never rendered; tests and the merge read it. source: "registry" | "archive" | "slot"; }; diff --git a/common/views/jobRows.ts b/common/views/jobRows.ts @@ -2,6 +2,7 @@ import { isDrainableKind } from "../jobs/jobKinds"; import { jobIdTime } from "../jobs/ulid"; import type { JobRecord } from "../jobs/registry"; import type { JobListEntry } from "../jobs/listJobs"; +import { jobSpecDetail } from "../jobs/jobDetail"; import type { ChannelStat } from "../controller/channels"; import type { QueueView } from "../jobs/scheduler"; import type { JobRowView, StuckReason } from "./jobRowView"; @@ -251,6 +252,7 @@ export function fromRecord(j: JobRecord, ctx: FromRecordContext): JobRowView { inRegistry: true, replayable: Boolean(j.spec), position: ctx.position >= 0 ? ctx.position : undefined, + detail: jobSpecDetail(j.kind, j.spec), source: "registry", }; } @@ -272,6 +274,7 @@ export function fromEntry(e: JobListEntry): JobRowView { logSize: e.logSize, inRegistry: e.inRegistry, replayable: e.replayable, + detail: e.detail, source: "archive", }; } diff --git a/editor/app/channels/[slug]/videos/[id]/components/VideoPanel.tsx b/editor/app/channels/[slug]/videos/[id]/components/VideoPanel.tsx @@ -10,6 +10,7 @@ import { } from "yt-dlp-transcript-common/lib/channelConfig"; import type { DownloadOutcomeRecord } from "yt-dlp-transcript-common/lib/downloadOutcome"; import type { SavedVideoPointer } from "yt-dlp-transcript-common/lib/savedVideo"; +import type { ClipProvenance } from "yt-dlp-transcript-common/lib/clipWindow"; import type { AvailabilityHistoryEntry } from "yt-dlp-transcript-common/lib/availability"; import type { SubtitleProvenance } from "yt-dlp-transcript-common/lib/subtitleProvenance"; import { formatBytes, formatDuration } from "yt-dlp-transcript-common/lib/format"; @@ -80,6 +81,9 @@ type Props = { // This video's saved-video pointer when its source container is persisted to // the store, else null. Drives the Source-video persistence card (Phase 5). savedVideo?: SavedVideoPointer | null; + // Windows of this video's source media another tool asked the editor to + // fetch, into data/<id>/clips/. Empty for almost every video. + clipWindows?: FetchedWindow[]; // Transcript coverage vs. video duration. When `incomplete` the transcript // covers only a fraction of the runtime (truncated audio download) — surfaced // as a warning banner with a re-download/re-transcribe action. Absent when @@ -158,6 +162,7 @@ export function VideoPanel({ doNotClean = false, excludedFromTruncatedCheck = false, savedVideo = null, + clipWindows = [], coverage = null, prevHref, nextHref, @@ -424,6 +429,20 @@ export function VideoPanel({ /> </PipelineStageCard> + {clipWindows.length > 0 && ( + <PipelineStageCard + id="fetched-windows" + title="Fetched windows" + summary={`${clipWindows.length} window${ + clipWindows.length === 1 ? "" : "s" + } of this video's source media, fetched for another tool.`} + defaultOpen={false} + tone="neutral" + > + <FetchedWindows windows={clipWindows} /> + </PipelineStageCard> + )} + <PipelineStageCard id="archive-media" title="Archive media" @@ -1138,6 +1157,74 @@ function SourceVideoSection({ ); } +// A window of source media this editor fetched because another tool asked. +export type FetchedWindow = { + file: string; + from: number; + to: number; + bytes: number; + provenance: ClipProvenance | null; +}; + +// WHO ASKED, FOR WHAT, AND WHY — the three things a `clips/` directory has to +// be able to answer months later, when the only other record (the job log) has +// been pruned. Modelled on PersistedSourceVideo below, which answers the same +// question for a whole container. +function FetchedWindows({ windows }: { windows: FetchedWindow[] }) { + return ( + <div className="flex flex-col gap-3"> + <p className="text-sm text-muted-foreground"> + Seconds of this video&apos;s source media, fetched through the managed + download path because a tool asked for them, and kept in{" "} + <code>clips/</code> so the next tool reuses them instead of paying for + the same bytes again. + </p> + <ul className="flex flex-col gap-2"> + {windows.map((w) => { + const p = w.provenance; + const what = [p?.manifest, p?.clipId].filter(Boolean).join("/"); + return ( + <li + key={w.file} + className="flex flex-col gap-0.5 text-sm border-t border-border pt-2 first:border-t-0 first:pt-0" + > + <span className="font-mono tabular-nums"> + {w.from.toFixed(2)}–{w.to.toFixed(2)}{" "} + <span className="text-muted-foreground"> + ({formatDuration(w.to - w.from)}) · {formatBytes(w.bytes)} + </span> + </span> + <span className="text-xs text-muted-foreground"> + {p ? ( + <> + requested by {p.requestedBy} + {what ? ` for ${what}` : ""} + {p.reason ? ` · \u201c${p.reason}\u201d` : ""} + {p.fetchedAt + ? ` · ${new Date(p.fetchedAt).toLocaleString()}` + : ""} + </> + ) : ( + "no provenance sidecar — requester unknown" + )} + </span> + </li> + ); + })} + </ul> + {/* A WINDOW IS NOT A DOWNLOAD. Thirty seconds of a stream is not the + video, and nothing downstream treats it as one: the fetch writes no + metadata.info.json, so this video stays exactly as undownloaded (and + as absent from the index) as it was. */} + <p className="text-xs text-muted-foreground"> + Fetched windows do not make this video downloaded: they carry no + metadata, so the video&apos;s pipeline state and its place in the index + are unchanged. + </p> + </div> + ); +} + // The persisted-details view: a sibling of the run panel above, never a // replacement for it. function PersistedSourceVideo({ @@ -1172,6 +1259,27 @@ function PersistedSourceVideo({ <dd>{savedVideo.keepReason}</dd> </> )} + {/* WHO ASKED. `keepReason` cannot say it — it stays override/pin so the + retention prune leaves this container alone — so "why is this 4 GB + file here" needs its own line. */} + {savedVideo.origin && ( + <> + <dt className="text-muted-foreground">Requested by</dt> + <dd> + {savedVideo.origin.requestedBy} + {[savedVideo.origin.manifest, savedVideo.origin.clipId] + .filter(Boolean) + .join("/") + ? ` for ${[savedVideo.origin.manifest, savedVideo.origin.clipId] + .filter(Boolean) + .join("/")}` + : ""} + {savedVideo.origin.reason + ? ` \u2014 \u201c${savedVideo.origin.reason}\u201d` + : ""} + </dd> + </> + )} {savedVideo.sha256 && ( <> <dt className="text-muted-foreground">sha256</dt> diff --git a/editor/app/channels/[slug]/videos/[id]/page.tsx b/editor/app/channels/[slug]/videos/[id]/page.tsx @@ -7,6 +7,7 @@ import type { Dirent } from "node:fs"; import { readChannelConfig } from "yt-dlp-transcript-common/controller/channels"; import { loadDownloadOutcome } from "yt-dlp-transcript-common/lib/downloadOutcome-server"; import { loadAvailability } from "yt-dlp-transcript-common/lib/availability-server"; +import { listClipWindows } from "yt-dlp-transcript-common/lib/clipWindow-server"; import { isDoNotClean } from "yt-dlp-transcript-common/lib/doNotClean-server"; import { isExcludedFromTruncatedCheck } from "yt-dlp-transcript-common/lib/excludeTruncatedCheck-server"; import { loadSavedVideo } from "yt-dlp-transcript-common/lib/savedVideo-server"; @@ -119,6 +120,10 @@ export default async function VideoDetailPage({ const excludedFromTruncatedCheck = await isExcludedFromTruncatedCheck(videoDir); const savedVideo = await loadSavedVideo(videoDir); + // The windows another tool asked this editor to fetch. One readdir of + // data/<id>/clips/ plus a stat per file — and no per-CHANNEL count anywhere, + // because that would be a walk of every video dir to draw one number. + const clipWindows = await listClipWindows(videoDir); // Where each subtitle track came from, so the panel can say "YouTube // auto-captions" vs "manual captions" — and offer to replace the former with a // transcript of our own. One 4 KB head read per VTT (see subtitleProvenance). @@ -228,6 +233,13 @@ export default async function VideoDetailPage({ doNotClean={doNotClean} excludedFromTruncatedCheck={excludedFromTruncatedCheck} savedVideo={savedVideo} + clipWindows={clipWindows.map((w) => ({ + file: w.file, + from: w.from, + to: w.to, + bytes: w.bytes, + provenance: w.provenance, + }))} coverage={coverage} /> diff --git a/editor/app/jobs/components/JobsTable.tsx b/editor/app/jobs/components/JobsTable.tsx @@ -416,6 +416,13 @@ export function JobsTable({ <td className="px-3 py-2 align-top" title={j.kind || undefined}> <span className="flex items-center gap-1.5"> {j.kind ? jobKindLabel(j.kind) : "—"} + {/* See RunningJobsList: the kind alone cannot say which + tool asked for which clip. */} + {j.detail && ( + <span className="text-xs text-muted-foreground"> + · {j.detail} + </span> + )} {j.background && ( <span className="text-[10px] uppercase tracking-wide px-1.5 py-0.5 rounded bg-warning-soft text-warning" diff --git a/editor/app/jobs/components/RunningJobsList.tsx b/editor/app/jobs/components/RunningJobsList.tsx @@ -69,6 +69,12 @@ function JobRow({ <span className="text-xs font-medium" title={job.kind}> {jobKindLabel(job.kind)} </span> + {/* WHO ASKED, beside the kind. "Fetch window" alone is the least + useful row on the page: every window fetch has that kind, and the + question is which tool wanted which clip. */} + {job.detail && ( + <span className="text-xs text-muted-foreground">· {job.detail}</span> + )} {job.background && ( <span className="text-[10px] uppercase tracking-wide px-1.5 py-0.5 rounded bg-warning-soft text-warning"