Cache preview frames for Discord video attachments
This commit is contained in:
@@ -193,18 +193,17 @@ const DISCORD_HOSTS = new Set(["cdn.discordapp.com", "media.discordapp.net"]);
|
||||
/**
|
||||
* The preview target for a media row.
|
||||
*
|
||||
* Attachments and stickers: their Discord-hosted URL, resized to a small WebP.
|
||||
* Attachments and stickers: their Discord-hosted URL, resized to a small WebP. Discord's proxy
|
||||
* also renders a frame for video files, so attachment videos get a poster for free.
|
||||
* Embeds: the stored thumbnail or image URL, used as-is (Discord-hosted ones are still resized).
|
||||
* Returns null when the row has no preview source.
|
||||
* A bare embed URL is a page, never a preview. Returns null when the row has no preview source.
|
||||
*/
|
||||
export function previewUrlFor(media: {
|
||||
url: string;
|
||||
kind: string;
|
||||
previewUrl?: string | null;
|
||||
}): string | null {
|
||||
const candidate =
|
||||
media.previewUrl ??
|
||||
(media.kind === "image" || media.kind === "gif" ? media.url : null);
|
||||
const candidate = media.previewUrl ?? media.url;
|
||||
if (!candidate) return null;
|
||||
|
||||
try {
|
||||
@@ -214,8 +213,13 @@ export function previewUrlFor(media: {
|
||||
url.searchParams.set("width", "320");
|
||||
url.searchParams.set("height", "320");
|
||||
url.searchParams.set("format", "webp");
|
||||
return url.toString();
|
||||
}
|
||||
return url.toString();
|
||||
|
||||
if (media.previewUrl || media.kind === "image" || media.kind === "gif") {
|
||||
return url.toString();
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -342,7 +342,8 @@ export class ScrapeStore {
|
||||
* embed thumbnails only when `includeEmbeds` is set (download mode).
|
||||
*/
|
||||
pendingPreviews(limit = 5000, includeEmbeds = false): MediaRow[] {
|
||||
const local = "(kind IN ('image', 'gif') AND (source IS NULL OR source != 'embed'))";
|
||||
const local =
|
||||
"((source IS NULL OR source != 'embed') AND kind IN ('image', 'gif', 'video'))";
|
||||
const condition = includeEmbeds
|
||||
? `(${local} OR (source = 'embed' AND preview_url IS NOT NULL))`
|
||||
: local;
|
||||
|
||||
@@ -522,6 +522,10 @@ void (async () => {
|
||||
"https://media.discordapp.net/attachments/1/2/pic.png?width=320&height=320&format=webp"
|
||||
);
|
||||
assert.equal(previewUrlFor({ url: "https://www.youtube.com/watch?v=x", kind: "video" }), null);
|
||||
assert.equal(
|
||||
previewUrlFor({ url: "https://cdn.discordapp.com/attachments/1/2/clip.mp4?ex=abc", kind: "video" }),
|
||||
"https://media.discordapp.net/attachments/1/2/clip.mp4?ex=abc&width=320&height=320&format=webp"
|
||||
);
|
||||
assert.equal(
|
||||
previewUrlFor({
|
||||
url: "https://www.youtube.com/watch?v=x",
|
||||
@@ -599,7 +603,10 @@ void (async () => {
|
||||
{
|
||||
messages: [
|
||||
message("600", {
|
||||
attachments: [attachment({ filename: "prev.png", contentType: "image/png" })],
|
||||
attachments: [
|
||||
attachment({ filename: "prev.png", contentType: "image/png" }),
|
||||
attachment({ filename: "clip.mp4", contentType: "video/mp4" })
|
||||
],
|
||||
embeds: [
|
||||
{
|
||||
type: "video",
|
||||
@@ -643,9 +650,10 @@ void (async () => {
|
||||
|
||||
const meta = engine.get(started.id);
|
||||
assert.equal(meta?.state, "done");
|
||||
assert.equal(meta?.previewed, 2);
|
||||
assert.equal(meta?.previewed, 3);
|
||||
assert.equal(fetched.some(url => url.includes("ytimg.com")), true);
|
||||
assert.equal(fetched.some(url => url.includes("youtube.com/watch")), false);
|
||||
assert.equal(fetched.some(url => url.includes("clip.mp4") && url.includes("format=webp")), true);
|
||||
|
||||
const store = new ScrapeStore(join(root, started.id));
|
||||
const row = store.queryMedia({ kind: "image" })[0];
|
||||
|
||||
Reference in New Issue
Block a user