commit e082d44f08184d7b1d5c3199c273033a94b6ed3a
parent a876362bfbc5e25da67265d18f90631a961f6c6e
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 25 Sep 2026 12:38:52 -0400
editor: keepVideosAction — the bulk form of the do-not-clean toggle
Revalidates each marked video and the channel page, and requests the
channel snapshot ONCE per batch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
1 file changed, 47 insertions(+), 0 deletions(-)
diff --git a/editor/app/channels/[slug]/videos/[id]/videoActions.ts b/editor/app/channels/[slug]/videos/[id]/videoActions.ts
@@ -29,6 +29,13 @@ import {
} from "yt-dlp-transcript-common/lib/queueKeys";
import { readChannelConfig } from "yt-dlp-transcript-common/controller/channels";
import { setDoNotClean } from "yt-dlp-transcript-common/lib/doNotClean-server";
+import {
+ KeepVideosError,
+ keepVideosMatching,
+ type KeepVideosField,
+ type KeepVideosResult,
+} from "yt-dlp-transcript-common/controller/keepVideosMatching";
+import { ChannelMediaUnreachableError } from "yt-dlp-transcript-common/lib/channelMedia";
import { setExcludedFromTruncatedCheck } from "yt-dlp-transcript-common/lib/excludeTruncatedCheck-server";
import { pruneFailedTranscriptions } from "yt-dlp-transcript-common/controller/failedTranscriptions";
import { transcodeAudio } from "yt-dlp-transcript-common/controller/transcode";
@@ -640,6 +647,46 @@ export async function toggleDoNotCleanAction(
return { ok: true };
}
+// The BULK form of the toggle above: set the do-not-clean marker on every video
+// of one channel whose title / description matches `pattern` (the download
+// filter's matcher and subject — see common/controller/keepVideosMatching.ts).
+// No UI calls it yet; `pnpm ops keep-videos` does. Same revalidation as the
+// toggle, per marked video, and ONE snapshot request for the whole batch — the
+// snapshot's cleanup buckets exclude kept ids, so it has to be re-derived, but
+// once, not once per video.
+export async function keepVideosAction(input: {
+ slug: string;
+ pattern: string;
+ fields?: KeepVideosField[];
+ note?: string;
+ dryRun?: boolean;
+}): Promise<{ ok: true; result: KeepVideosResult } | { ok: false; error: string }> {
+ let result: KeepVideosResult;
+ try {
+ result = await keepVideosMatching({
+ paths: getPaths(),
+ channelSlug: input.slug,
+ pattern: input.pattern,
+ fields: input.fields,
+ note: input.note,
+ dryRun: input.dryRun,
+ });
+ } catch (e) {
+ if (e instanceof KeepVideosError || e instanceof ChannelMediaUnreachableError) {
+ return { ok: false, error: e.message };
+ }
+ throw e;
+ }
+ if (result.marked > 0) {
+ for (const m of result.matched) {
+ if (m.marked) revalidatePath(`/channels/${input.slug}/videos/${m.id}`);
+ }
+ revalidatePath(`/channels/${input.slug}`);
+ requestChannelSnapshot(getPaths(), input.slug);
+ }
+ return { ok: true, result };
+}
+
// Opt this video in/out of the truncated/incomplete-transcript check. When
// enabled, the snapshot's incompleteTranscript + shortAudio buckets and the
// video panel's "looks truncated" banner suppress this id (for videos that