Files
eh-downloader/thumbnail/ffmpeg_binary.ts

127 lines
3.8 KiB
TypeScript

import { ThumbnailFormat } from "../config.ts";
import { ThumbnailAlign } from "./base.ts";
import { type ThumbnailConfig, ThumbnailGenMethod } from "./base.ts";
import { base_logger } from "../utils/logger.ts";
const logger = base_logger.get_logger("thumbnail-ffmpeg-binary");
export async function check_ffmpeg_binary(p: string) {
const cmd = new Deno.Command(p, {
stdout: "null",
stderr: "null",
args: ["-h"],
});
const c = cmd.spawn();
const o = await c.output();
return o.code === 0;
}
export async function fb_get_size(i: string) {
const cmd = new Deno.Command("ffprobe", {
stdout: "piped",
stderr: "piped",
args: [
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"stream=width,height",
"-of",
"csv=s=x:p=0",
i,
],
});
const c = cmd.spawn();
const o = await c.output();
if (o.code !== 0) {
return null;
}
const s = (new TextDecoder()).decode(o.stdout).trim().split("x");
return {
width: parseInt(s[0]),
height: parseInt(s[1]),
};
}
export async function fb_generate_thumbnail(
p: string,
i: string,
o: string,
cfg: ThumbnailConfig,
fmt: ThumbnailFormat,
) {
let add = "";
const codec = fmt == ThumbnailFormat.WEBP ? "libwebp" : "mjpeg";
if (cfg.method == ThumbnailGenMethod.Cover) {
const size = cfg.input ?? await fb_get_size(i);
if (!size) return false;
const twidth = Math.floor(size.width * cfg.height / size.height);
const frwidth = Math.floor(twidth > cfg.width ? twidth : cfg.width);
const frheight = Math.floor(
twidth > cfg.width
? cfg.height
: size.height * cfg.width / size.width,
);
const xy = cfg.align == ThumbnailAlign.Center
? ""
: cfg.align == ThumbnailAlign.Left
? ":x=0:y=0"
: `:x=${frwidth - cfg.width}:y=${frheight - cfg.height}`;
add =
`scale=${frwidth}x${frheight},crop=${cfg.width}:${cfg.height}${xy}`;
} else if (cfg.method == ThumbnailGenMethod.Contain) {
const size = cfg.input ?? await fb_get_size(i);
if (!size) return false;
const twidth = Math.floor(size.width * cfg.height / size.height);
const frwidth = Math.floor(twidth > cfg.width ? cfg.width : twidth);
const frheight = Math.floor(
twidth > cfg.width
? size.height * cfg.width / size.width
: cfg.height,
);
const xy = cfg.align == ThumbnailAlign.Center
? `:x=${Math.floor((cfg.width - frwidth) / 2)}:y=${
Math.floor((cfg.height - frheight) / 2)
}`
: cfg.align == ThumbnailAlign.Left
? ""
: `:x=${cfg.width - frwidth}:y=${cfg.height - frheight}`;
add =
`scale=${frwidth}x${frheight},pad=${cfg.width}:${cfg.height}${xy}:color=white`;
} else {
add = `scale=${cfg.width}:${cfg.height}`;
}
const args = [
"-n",
"-i",
i,
"-c",
codec,
"-vf",
add,
"-qmin",
`${cfg.quality}`,
"-qmax",
`${cfg.quality}`,
"-f",
"image2",
];
if (fmt == ThumbnailFormat.WEBP) {
args.push("-quality", "100");
}
args.push(o);
const cmd = new Deno.Command(p, { args, stdout: "null", stderr: "piped" });
const c = cmd.spawn();
const s = await c.output();
if (s.code !== 0) {
try {
const d = (new TextDecoder()).decode(s.stderr);
logger.warn(d);
} catch (_) {
logger.warn(s.stderr);
}
}
return s.code === 0;
}