fp/services/our/src/tasks/createVideoThumbnail.ts
CJ_Clippy 110565d536
Some checks failed
ci / build (push) Failing after 0s
ci / Tests & Checks (push) Failing after 1s
add funscripts tests
2025-07-18 03:50:29 -08:00

118 lines
3.4 KiB
TypeScript

import type { Task, Helpers } from "graphile-worker";
import { PrismaClient } from "../../generated/prisma";
import { withAccelerate } from "@prisma/extension-accelerate";
import { getOrDownloadAsset } from "../utils/cache";
import { env } from "../config/env";
import { S3Client } from "@aws-sdk/client-s3";
import { getS3Client, uploadFile } from "../utils/s3";
import { nanoid } from "nanoid";
import { getNanoSpawn } from "../utils/nanoSpawn";
const prisma = new PrismaClient().$extends(withAccelerate());
interface Payload {
vodId: string;
}
async function createThumbnail(helpers: Helpers, inputFilePath: string) {
helpers.logger.debug(`createThumbnail with inputFilePath=${inputFilePath}`)
if (!inputFilePath) {
throw new Error("inputFilePath is missing");
}
const outputFilePath = inputFilePath.replace(/\.[^/.]+$/, '') + '-thumb.png';
const spawn = await getNanoSpawn();
const result = await spawn('vcsi', [
inputFilePath,
'--metadata-position', 'hidden',
'--metadata-margin', '0',
'--metadata-horizontal-margin', '0',
'--metadata-vertical-margin', '0',
'--grid-spacing', '0',
'--grid-horizontal-spacing', '0',
'--grid-vertical-spacing', '0',
'--timestamp-horizontal-margin', '0',
'--timestamp-vertical-margin', '0',
'--timestamp-horizontal-padding', '0',
'--timestamp-vertical-padding', '0',
'-w', '830',
'-g', '5x5',
'-o', outputFilePath
], {
stdout: 'inherit',
stderr: 'inherit',
});
// const exitCode = await subprocess;
// if (exitCode !== 0) {
// console.error(`vcsi failed with exit code ${exitCode}`);
// process.exit(exitCode);
// }
helpers.logger.debug('result as follows')
helpers.logger.debug(JSON.stringify(result, null, 2))
helpers.logger.info(`✅ Thumbnail saved to: ${outputFilePath}`);
return outputFilePath
}
function assertPayload(payload: any): asserts payload is Payload {
if (typeof payload !== "object" || !payload) throw new Error("invalid payload-- was not an object.");
if (typeof payload.vodId !== "string") throw new Error("invalid payload-- was missing vodId");
}
export default async function createVideoThumbnail(payload: any, helpers: Helpers) {
assertPayload(payload)
const { vodId } = payload
const vod = await prisma.vod.findFirstOrThrow({
where: {
id: vodId
}
})
// * [x] load vod
// * [x] exit if video.thumbnail already defined
if (vod.thumbnail) {
helpers.logger.info(`Doing nothing-- vod ${vodId} already has a thumbnail.`)
return; // Exit the function early
}
if (!vod.sourceVideo) {
throw new Error(`Failed to create thumbnail-- vod ${vodId} is missing a sourceVideo.`);
}
helpers.logger.info('Creating Video Thumbnail')
const s3Client = getS3Client()
// * [x] download video segments from pull-thru cache
const videoFilePath = await getOrDownloadAsset(s3Client, env.S3_BUCKET, vod.sourceVideo)
console.log(`videoFilePath=${videoFilePath}`)
// * [x] run vcsi
const thumbnailPath = await createThumbnail(helpers, videoFilePath)
console.log(`thumbnailPath=${thumbnailPath}`)
// * [x] generate thumbnail s3 key
const s3Key = `/thumb/${nanoid()}`
// * [x] upload thumbnail to s3
await uploadFile(s3Client, env.S3_BUCKET, s3Key, thumbnailPath, 'image/png')
// * [x] update vod record
await prisma.vod.update({
where: { id: vodId },
data: { thumbnail: s3Key }
});
// * [x] done
}