Add video poster frames and rework the media status row

This commit is contained in:
Fisto
2026-09-16 12:40:10 +02:00
parent f9b4041157
commit 043d1cd3c5
9 changed files with 124 additions and 12 deletions
+49 -1
View File
@@ -5,7 +5,7 @@
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { writeFileSync } from "node:fs";
import { readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { promisify } from "node:util";
import type { MediaRow } from "./store.js";
@@ -228,6 +228,54 @@ export async function downloadPreview(
}
}
/**
* Grab a poster frame from a downloaded video with ffmpeg.
* Discord has no server-side thumbnails for uploaded videos, so the archive makes its own.
*/
export async function videoPoster(
inputPath: string,
outDir: string,
ffmpeg = process.env.VESKSLOP_FFMPEG ?? "ffmpeg"
): Promise<{ hash: string; bytes: number } | null> {
const temp = join(outDir, `.poster-${Date.now()}-${Math.random().toString(16).slice(2)}.webp`);
try {
await execFileAsync(
ffmpeg,
[
"-v",
"error",
"-y",
"-ss",
"0.5",
"-i",
inputPath,
"-frames:v",
"1",
"-vf",
"scale=480:-1",
temp
],
{ timeout: 30_000 }
);
const buffer = readFileSync(temp);
if (!buffer.length) return null;
const hash = createHash("sha256").update(buffer).digest("hex");
renameSync(temp, join(outDir, `${hash}.webp`));
return { hash, bytes: buffer.length };
} catch {
return null;
} finally {
try {
rmSync(temp, { force: true });
} catch {
// already renamed
}
}
}
/** The gap before the next file, in milliseconds. */
export function downloadGapMs(options: DownloadOptions = {}): number {
const min = options.minGapMs ?? 1000;
+9
View File
@@ -10,6 +10,7 @@ import {
downloadOne,
downloadPreview,
probeDimensions,
videoPoster,
type DownloadOptions
} from "./download.js";
import {
@@ -324,6 +325,14 @@ export class ScrapeEngine {
);
if (dimensions) store.updateDimensions(row.id, dimensions.width, dimensions.height);
}
if (row.kind === "video" && !row.preview_hash) {
const poster = await videoPoster(join(targetDir, outcome.path), join(dir, "thumbs"));
if (poster) {
store.markPreview(row.id, poster);
meta.previewed = (meta.previewed ?? 0) + 1;
}
}
} else {
store.markDownloadError(row.id, outcome.error ?? outcome.state);
if (outcome.state === "failed") meta.downloadErrors = (meta.downloadErrors ?? 0) + 1;
+2
View File
@@ -19,6 +19,8 @@ export {
downloadOne,
downloadPreview,
previewUrlFor,
probeDimensions,
videoPoster,
type DownloadOptions,
type DownloadOutcome
} from "./download.js";