Archilyzer · Source

archilyzer

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

commit 80680d21b0af285c763a30d869bf180fce426dd4
parent 3a79cb922069221041e9845251396abab112c703
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri, 25 Sep 2026 13:50:34 -0400

channels: the video page shows the listing scan's metadata for an undownloaded video

loadMeta moves onto readVideoMetadataForDisplay (metadata.info.json, else the
channel's metadata-scan.json entry) and the page's private parser goes. A
scan-sourced header carries "from the listing scan — not downloaded"
(aria-label "metadata source"); a Description <details>, collapsed, shows
whenever either source has one. New e2e video-titles.spec.ts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Meditor/app/channels/[slug]/videos/[id]/page.tsx | 57+++++++++++++++++++++++++--------------------------------
Aeditor/e2e/video-titles.spec.ts | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 140 insertions(+), 32 deletions(-)

diff --git a/editor/app/channels/[slug]/videos/[id]/page.tsx b/editor/app/channels/[slug]/videos/[id]/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import path from "node:path"; -import { readdir, readFile, stat } from "node:fs/promises"; +import { readdir, stat } from "node:fs/promises"; 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"; @@ -13,6 +13,10 @@ import { isExcludedFromTruncatedCheck } from "yt-dlp-transcript-common/lib/exclu import { loadSavedVideo } from "yt-dlp-transcript-common/lib/savedVideo-server"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { + readVideoMetadataForDisplay, + type VideoDisplayMetadata, +} from "yt-dlp-transcript-common/controller/videoTitles"; +import { isTranscriptVtt, resolvePrimaryVtt, } from "yt-dlp-transcript-common/lib/videoStatus"; @@ -38,14 +42,6 @@ import { loadVideoOperationPanels } from "./lib/videoOperationPanels"; export const dynamic = "force-dynamic"; -type VideoMeta = { - title?: string; - webpageUrl?: string; - uploader?: string; - uploadDate?: string; - duration?: number; -}; - async function loadVideoDir( slug: string, videoId: string, @@ -68,29 +64,11 @@ async function loadVideoDir( return { files }; } -async function loadMeta(slug: string, videoId: string): Promise<VideoMeta> { - const file = path.join( - getPaths().channelsDir, - slug, - "data", - videoId, - "metadata.info.json", - ); - try { - const raw = await readFile(file, "utf8"); - const j = JSON.parse(raw); - return { - title: typeof j?.title === "string" ? j.title : undefined, - webpageUrl: - typeof j?.webpage_url === "string" ? j.webpage_url : undefined, - uploader: typeof j?.uploader === "string" ? j.uploader : undefined, - uploadDate: - typeof j?.upload_date === "string" ? j.upload_date : undefined, - duration: typeof j?.duration === "number" ? j.duration : undefined, - }; - } catch { - return {}; - } +// metadata.info.json when the video was downloaded, else the channel's +// metadata-scan.json entry for a listed-but-never-downloaded video — so an +// undownloaded video's page is not a bare id. +function loadMeta(slug: string, videoId: string): Promise<VideoDisplayMetadata> { + return readVideoMetadataForDisplay(getPaths(), slug, videoId); } export async function generateMetadata({ @@ -216,7 +194,22 @@ export default async function VideoDetailPage({ {typeof meta.duration === "number" && ( <span>{formatDuration(meta.duration)}</span> )} + {meta.source === "scan" && ( + <span aria-label="metadata source" className="italic"> + from the listing scan — not downloaded + </span> + )} </div> + {meta.description && ( + <details className="text-sm"> + <summary className="cursor-pointer text-muted-foreground hover:text-foreground"> + Description + </summary> + <p className="mt-2 whitespace-pre-wrap break-words text-muted-foreground max-h-96 overflow-y-auto"> + {meta.description} + </p> + </details> + )} </header> <RunningJobsList jobs={activeJobs} hideChannelSlug hideVideoId /> diff --git a/editor/e2e/video-titles.spec.ts b/editor/e2e/video-titles.spec.ts @@ -0,0 +1,115 @@ +// The per-channel video list shows what each video is CALLED, and searches by +// it; an undownloaded video's own page is not a bare id. +// +// Three rows, one per title source that can reach a fixture (the transcript +// index is exercised by common/controller/videoTitles.test.ts): +// - fake00000001 — downloaded; its title is in data/<id>/metadata.info.json. +// - fake00000002 — listed, never downloaded; titled by the channel's +// metadata-scan.json (the listing scan's store). +// - fake00000003 — listed, never downloaded, never scanned: the bare id. + +import { writeFile } from "node:fs/promises"; +import { test, expect } from "@playwright/test"; +import { + channelVideos, + generateReport, + resetData, + resolvePath, +} from "./helpers"; + +const CHANNEL = "test-youtube"; +const ROOT = `test-transcripts/channels/${CHANNEL}`; +const DOWNLOADED = "fake00000001"; +const SCANNED = "fake00000002"; +const BARE = "fake00000003"; +const SCANNED_TITLE = "Moonlit Harbor Interview"; +const SCANNED_DESCRIPTION = "A long talk on the pier.\nSecond line of notes."; + +async function seed() { + await resetData("youtube-with-playlist"); + // The store's on-disk shape (common/controller/metadataScanStore.ts, + // METADATA_SCAN_FILENAME) — the scan writes exactly this, and never a + // data/<id>/ directory. + await writeFile( + resolvePath(`${ROOT}/metadata-scan.json`), + JSON.stringify( + { + version: 1, + updatedAt: "2026-09-25T00:00:00.000Z", + lastRun: null, + entries: { + [SCANNED]: { + title: SCANNED_TITLE, + description: SCANNED_DESCRIPTION, + uploadDate: "20240315", + duration: 754, + scannedAt: "2026-09-25T00:00:00.000Z", + }, + }, + errors: {}, + }, + null, + 2, + ), + ); +} + +test("the video list shows titles and searches them", async ({ page }) => { + await seed(); + await generateReport(page, CHANNEL); + await page.goto(channelVideos(CHANNEL)); + + const list = page.getByLabel("videos", { exact: true }); + const downloaded = list.getByLabel(`open ${DOWNLOADED}`); + const scanned = list.getByLabel(`open ${SCANNED}`); + const bare = list.getByLabel(`open ${BARE}`); + // Titles where known, the id beneath them; the bare id alone otherwise. + await expect(downloaded).toContainText("Synthetic Test Video 1"); + await expect(downloaded).toContainText(DOWNLOADED); + await expect(scanned).toContainText(SCANNED_TITLE); + await expect(scanned).toContainText(SCANNED); + await expect(bare).toHaveText(BARE); + + // A word from the scan-store title, in the wrong case, leaves that one row. + const search = page.getByLabel("search videos"); + await search.fill("harbor"); + await expect(scanned).toBeVisible(); + await expect(list.getByLabel(/^open fake/)).toHaveCount(1); + + // The id still matches, as it always has. + await search.fill(BARE); + await expect(bare).toBeVisible(); + await expect(list.getByLabel(/^open fake/)).toHaveCount(1); +}); + +test("an undownloaded video's page shows the listing scan's metadata", async ({ + page, +}) => { + await seed(); + await page.goto(`/channels/${CHANNEL}/videos/${SCANNED}`); + + await expect( + page.getByRole("heading", { name: SCANNED_TITLE }), + ).toBeVisible(); + await expect(page.getByLabel("metadata source")).toHaveText( + "from the listing scan — not downloaded", + ); + // Date and duration come from the scan entry too. + await expect(page.getByText("2024-03-15")).toBeVisible(); + await expect(page.getByText("12:34")).toBeVisible(); + + // Collapsed by default; opening it shows the description. + const summary = page.getByText("Description", { exact: true }); + await expect(summary).toBeVisible(); + const body = page.getByText("A long talk on the pier."); + await expect(body).toBeHidden(); + await summary.click(); + await expect(body).toBeVisible(); + + // A downloaded video's page has no scan note. + await page.goto(`/channels/${CHANNEL}/videos/${DOWNLOADED}`); + await expect( + page.getByRole("heading", { name: "Synthetic Test Video 1" }), + ).toBeVisible(); + await expect(page.getByLabel("metadata source")).toHaveCount(0); +});