118 lines
3.4 KiB
TypeScript
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
|
|
|
|
|
|
} |