92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
import type { Task, Helpers } from "graphile-worker";
|
|
import getVod from "@futureporn/fetchers/getVod.ts";
|
|
import { getStoryboard } from '@futureporn/utils/image.ts'
|
|
import { getCdnUrl, uploadFile, type S3FileArgs } from '@futureporn/storage/s3.ts'
|
|
import patchVodInDatabase from "@futureporn/fetchers/patchVodInDatabase.ts";
|
|
import { configs } from "../config";
|
|
|
|
interface Payload {
|
|
vod_id?: string;
|
|
video_url?: string;
|
|
}
|
|
|
|
function assertPayload(payload: any): asserts payload is Payload {
|
|
if (typeof payload !== "object" || !payload) throw new Error("invalid payload (it must be an object)");
|
|
if (typeof payload.vod_id !== "string" && typeof payload.video_url !== "string") throw new Error("payload requires either vod_id or video_url, however both were not a string.");
|
|
}
|
|
|
|
|
|
async function doIntegratedRequest(vodId: string): Promise<void> {
|
|
|
|
const vod = await getVod(vodId)
|
|
const s3_file = vod?.s3_file
|
|
if (!s3_file) throw new Error(`vod ${vodId} was missing a s3_file.`);
|
|
|
|
// we need to get a CDN url to the vod so we can download chunks of the file in order for Prevvy to create the storyboard image.
|
|
const cdnUrl = getCdnUrl(configs.s3MainBucket, s3_file.s3_key)
|
|
|
|
const thumbnailCdnUrl = await getThumbnailCdnUrl(cdnUrl)
|
|
|
|
await patchVodInDatabase(vodId, { thumbnail: thumbnailCdnUrl })
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
async function getThumbnailCdnUrl(videoUrl: string): Promise<string> {
|
|
|
|
const tmpImagePath = await getStoryboard(videoUrl)
|
|
|
|
// we need to upload the image to S3
|
|
const uploadArgs: S3FileArgs = {
|
|
filePath: tmpImagePath,
|
|
s3AccessKeyId: configs.s3AccessKeyId,
|
|
s3SecretAccessKey: configs.s3SecretAccessKey,
|
|
s3BucketName: configs.s3MainBucket,
|
|
s3Endpoint: configs.s3Region,
|
|
s3Region: configs.s3Region
|
|
}
|
|
|
|
const upload = await uploadFile(uploadArgs)
|
|
if (!upload) throw new Error(`failed to upload ${tmpImagePath} to S3`);
|
|
if (!upload.Key) throw new Error(`failed to upload ${tmpImagePath} to S3 (upload.Key was missing)`);
|
|
|
|
// we need to create a S3 file in the db
|
|
const thumbnail = getCdnUrl(configs.s3MainBucket, upload.Key)
|
|
|
|
return thumbnail
|
|
|
|
}
|
|
|
|
async function doSoloRequest(videoUrl: string): Promise<string> {
|
|
await getThumbnailCdnUrl(videoUrl)
|
|
}
|
|
|
|
|
|
export const generate_thumbnail: Task = async function (payload: unknown, helpers: Helpers) {
|
|
assertPayload(payload)
|
|
const { vod_id, video_url } = payload
|
|
|
|
helpers.logger.info(`🏗️ generate_thumbnail started with vod_id=${vod_id}, video_url=${video_url}`);
|
|
|
|
|
|
// Determine what kind of request is being made.
|
|
// It could be one of two scenarios
|
|
// * Here is a VOD record in the database, please update it's thumbnail. (integratedRequest)
|
|
// * Here is a video URL, please give us a thumbnail URL. (soloRequest)
|
|
//
|
|
const integratedRequest = (!!vod_id && !video_url)
|
|
const soloRequest = (!!video_url && !vod_id)
|
|
|
|
if (integratedRequest) {
|
|
await doIntegratedRequest(vod_id)
|
|
} else if (soloRequest) {
|
|
await getThumbnailCdnUrl(video_url)
|
|
} else {
|
|
throw new Error(`unsupported ambiguous request!`)
|
|
}
|
|
|
|
}
|
|
|
|
export default generate_thumbnail |