"use client"; import { useState, useTransition } from "react"; import { StreamActionLog } from "yt-dlp-transcript-common/components/StreamActionLog"; import type { ChannelHandling } from "yt-dlp-transcript-common/lib/channelConfig"; import type { SavedVideoPointer } from "yt-dlp-transcript-common/lib/savedVideo"; import { formatBytes } from "yt-dlp-transcript-common/lib/format"; import { DEFAULT_SOURCE_VIDEO_QUALITY, SOURCE_VIDEO_QUALITIES, SOURCE_VIDEO_QUALITY_LABELS, type SourceVideoQuality, } from "yt-dlp-transcript-common/ytdlp/downloadFormat"; import { QueueControl } from "../../../../../../components/QueueControl"; import { cancelJobAction } from "../../../../../../jobs/actions"; import { redownloadToArchiveAction, unpersistVideoAction, } from "../../videoActions"; export function SourceVideoSection({ slug, videoId, handling, defaultQueueKey, existingQueues, savedVideo, defaultQuality = DEFAULT_SOURCE_VIDEO_QUALITY, }: { slug: string; videoId: string; handling: ChannelHandling; defaultQueueKey: string; existingQueues: string[]; savedVideo: SavedVideoPointer | null; // The effective source-video quality (channel over global), resolved by the // page through resolveSourceVideoQuality: the select starts on it. defaultQuality?: SourceVideoQuality; }) { const [queueKey, setQueueKey] = useState(defaultQueueKey); const [quality, setQuality] = useState(defaultQuality); // Set the moment a persist run STARTS from this surface. That run's own // router.refresh() lands the saved-video pointer, and the persisted view used // to REPLACE this whole subtree — destroying the StreamActionLog below and the // log the operator was reading (plans/FACTS.md, "A run log lives in the // panel's React state"). One return, two fixed slots, and the run panel stays // mounted once it has been used; with no run it is still absent whenever the // video is already persisted, so the DOM is unchanged. const [ranHere, setRanHere] = useState(false); // EVERY CHANNEL GETS THE CONTROLS (release 10 slice N). A youtube-handling // channel used to be told persistence was for transcribe handling only — // which was true in effect, because its one media pass was skipped whenever a // transcript or captions existed. The run now forces that pass (forceMedia), // so the button does what it says on every channel. return (
{savedVideo ? ( ) : handling === "youtube" ? (

Download this video's full source container and move it into the saved-video store. YouTube's subtitles are fetched again first, as on any re-download; a Whisper transcript is not touched, and no audio is extracted beside a transcript.

) : (

Re-fetch this video's full source container and move it into the saved-video store, without disturbing the existing transcript. Use this to archive a video that was downloaded audio-only. (Videos in a channel's keep-latest window persist automatically on download.)

)} {savedVideo && !ranHere ? null : ( { setRanHere(true); return redownloadToArchiveAction( slug, videoId, queueKey, quality, ); }} cancelAction={cancelJobAction} buttonLabel="Persist source video" runningLabel="Archiving…" label={`Persist source video for ${videoId}`} extraControls={ <> } /> )}
); } // The persisted-details view: a sibling of the run panel above, never a // replacement for it. function PersistedSourceVideo({ slug, videoId, savedVideo, }: { slug: string; videoId: string; savedVideo: SavedVideoPointer; }) { const [pending, startTransition] = useTransition(); const [error, setError] = useState(null); return ( <>

The source container for this video is persisted to the saved-video store, so the Clean-audio sweep can reclaim its audio.*{" "} without losing the original. Transcription falls back to it when no audio is on disk.

File
{savedVideo.file}
Size
{formatBytes(savedVideo.bytes)}
Stored
{new Date(savedVideo.storedAt).toLocaleString()}
{/* WHAT WAS KEPT: the quality asked for and the format yt-dlp took. Absent on a pointer written before the quality setting. */} {savedVideo.format && ( <>
Format
{savedVideo.format.height ? `${savedVideo.format.height}p` : "unknown height"} {savedVideo.format.vcodec ? ` ${savedVideo.format.vcodec}` : ""} {` (${SOURCE_VIDEO_QUALITY_LABELS[savedVideo.format.preset]})`}
)} {savedVideo.keepReason && ( <>
Reason
{savedVideo.keepReason}
)} {/* 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 && ( <>
Requested by
{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` : ""}
)} {savedVideo.sha256 && ( <>
sha256
{savedVideo.sha256}
)}
Location
{savedVideo.dir}

Unpersist moves the container back into this video's data dir (where it appears under Files and plays inline) and removes the pointer.

{error && ( {error} )} ); }