commit 5a5201465b7834370cffeb64bf2420dfb6358eb6
parent 2fec224848f03a2d8347aa1b5abc92c38a080a91
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 7 Oct 2026 20:22:25 -0400
Merge fetch-window-url-platform-args (a window's URL picks its platform args)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/common/ytdlp/channelArgs.test.ts b/common/ytdlp/channelArgs.test.ts
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import {
channelExtraArgs,
channelPaceSeconds,
+ configForVideoUrl,
pacedPlatformArgs,
platformArgs,
platformArgsForUrl,
@@ -198,3 +199,20 @@ test("one-off imports have their own floor: Odysee's 60 s, BitChute's batch floo
assert.equal(platformImportMinGapSeconds(p), 0, String(p));
}
});
+
+test("a video's own URL picks the platform args when the channel names none or another", () => {
+ const notes = cfg({ handling: "transcribe" } as Partial<ChannelConfig>);
+ assert.deepEqual(channelExtraArgs(notes), []);
+ assert.deepEqual(
+ channelExtraArgs(configForVideoUrl(notes, "https://rumble.com/v7em13s-x.html")),
+ RUMBLE,
+ );
+ const yt = cfg({ url: "https://www.youtube.com/@x", ytdlpExtraArgs: ["--limit-rate", "1M"] });
+ assert.deepEqual(
+ channelExtraArgs(configForVideoUrl(yt, "https://rumble.com/v7em13s-x.html")),
+ [...RUMBLE, "--limit-rate", "1M"],
+ "the channel's own args still apply",
+ );
+ assert.equal(configForVideoUrl(yt, "https://www.youtube.com/watch?v=abc"), yt);
+ assert.equal(configForVideoUrl(yt, "https://example.com/v.mp4"), yt);
+});
diff --git a/common/ytdlp/channelArgs.ts b/common/ytdlp/channelArgs.ts
@@ -53,6 +53,18 @@ export function channelPlatform(
return config.platform ?? detectPlatform(config.url);
}
+// The config to build a VIDEO's argv from: the channel's, with the platform
+// taken from the video's own URL when that names a different one. A channel
+// may hold another platform's videos — community-notes has no url or platform
+// of its own and holds Rumble videos — and a Rumble request without Rumble's
+// args (`--impersonate`) is refused at Cloudflare. The request goes where the
+// URL points, so the URL's platform decides the args and the pace.
+export function configForVideoUrl(config: ChannelConfig, videoUrl: string): ChannelConfig {
+ const platform = detectPlatform(videoUrl);
+ if (!platform || platform === channelPlatform(config)) return config;
+ return { ...config, platform, url: videoUrl };
+}
+
// The key the pacing state is kept under — the same one the rate-limit
// cooldown uses (`detectPlatform(url) ?? "unknown"`, autoRunner/runYtdlp), so a
// 429 recorded by any path paces every path.
diff --git a/common/ytdlp/fetchWindowManaged.ts b/common/ytdlp/fetchWindowManaged.ts
@@ -41,7 +41,7 @@ import {
writeClipProvenance,
type ClipWindow,
} from "../lib/clipWindow-server";
-import { channelExtraArgs } from "./channelArgs";
+import { channelExtraArgs, configForVideoUrl } from "./channelArgs";
import { runOneYtdlp } from "./runOneYtdlp";
import { FULL_LOG_PROGRESS_ARGS } from "./downloadOneManaged";
import { clipFormatSelector } from "./downloadFormat";
@@ -210,7 +210,9 @@ export async function fetchWindowManaged(
"--merge-output-format",
"mp4",
...FULL_LOG_PROGRESS_ARGS,
- ...channelExtraArgs(opts.channelConfig, cookies),
+ // The video's URL picks the platform args: a channel may hold another
+ // platform's videos (configForVideoUrl).
+ ...channelExtraArgs(configForVideoUrl(opts.channelConfig, opts.videoUrl), cookies),
// THE NEGATIONS COME AFTER THE CHANNEL'S OWN ARGS, AND -o AFTER THOSE.
//
// `ytdlpExtraArgs` is free text an operator typed into a form; it is