import type { Metadata } from "next"; import Link from "next/link"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { getSettings } from "yt-dlp-transcript-common/lib/settings"; import { formatBytes } from "yt-dlp-transcript-common/lib/format"; import { listSavedVideos } from "yt-dlp-transcript-common/controller/savedVideoInventory"; import { readSchedulerState } from "yt-dlp-transcript-common/jobs/syncSchedulerState"; import { SavedVideosControls } from "./components/SavedVideosControls"; export const dynamic = "force-dynamic"; export const metadata: Metadata = { title: "Saved videos" }; type ChannelSummary = { slug: string; count: number; bytes: number; verified: number; }; export default async function SavedVideosPage() { const paths = getPaths(); const settings = getSettings(); const backup = settings.savedVideoBackup; // Channels whose drive is not answering are not read (one pointer read per // video dir, each of which would wait on it); the page names them. const notAnswering: string[] = []; const entries = await listSavedVideos({ paths, notAnswering }); notAnswering.sort(); const scheduler = await readSchedulerState(paths); const byChannel = new Map(); let totalBytes = 0; let totalVerified = 0; for (const e of entries) { const row = byChannel.get(e.slug) ?? { slug: e.slug, count: 0, bytes: 0, verified: 0 }; row.count += 1; row.bytes += e.pointer.bytes; if (e.pointer.sha256) { row.verified += 1; totalVerified += 1; } byChannel.set(e.slug, row); totalBytes += e.pointer.bytes; } const channels = [...byChannel.values()].sort((a, b) => a.slug.localeCompare(b.slug), ); const lastBackup = scheduler.lastSavedVideoBackupAt ? new Date(scheduler.lastSavedVideoBackupAt).toLocaleString() : null; return (

Saved videos

Set a channel's keep-latest window and store dir in its settings.

Default store dir: {paths.savedVideosDir}
Last backup: {lastBackup ? ( {lastBackup} ) : ( never )} {backup.enabled && backup.dest && ( {" "} ยท scheduled every {backup.intervalMinutes} min )}

By channel

{notAnswering.length > 0 && (

Not read, because the drive their media is on is not answering:{" "} {notAnswering.join(", ")}. Their saved videos are not in the counts above until it answers again (see{" "} Storage ).

)} {channels.length === 0 ? (

No saved videos yet. Set a channel's keep-latest window, then run a sync (or Persist kept now from its Cleanup stage).

) : ( {channels.map((c) => ( ))}
Channel Saved Size Checksummed
{c.slug} {c.count} {formatBytes(c.bytes)} {c.verified}/{c.count}
)}

Backups

); } function SummaryCard({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); }