import { createVideoClipper, YouTube, VideoType, ClipVideoParams, } from '@modal/webvideo-clip-core'; import { constants } from 'http2'; import { RouteHandlerMethod } from 'fastify'; export type ClipArgs = { url?: unknown, start?: string | number, end?: string | number, } const DURATION_STRING_REGEXP = /^\d\d:[0-5]\d:[0-5]\d(\.\d+)?$/; const validateRequestBody = (body: ClipArgs) => { const messages = [] as string[]; const { url, start, end } = body; if (typeof url !== 'string') { messages.push('URL is required.'); } const typeofStart = typeof start; if (typeofStart !== 'undefined') { if ( !['string', 'number'].includes(typeofStart) || (typeofStart === 'string' && !DURATION_STRING_REGEXP.test(start as string)) ) { messages.push('Invalid start value.'); } } const typeofEnd = typeof end; if (typeofEnd !== 'undefined') { if ( !['string', 'number'].includes(typeofEnd) || (typeofEnd === 'string' && !DURATION_STRING_REGEXP.test(end as string)) ) { messages.push('Invalid end value.'); } } return messages; }; const getVideoType = (url: string) => { if (url.startsWith('https://www.youtube.com')) { return YouTube.VIDEO_TYPE as VideoType; } return null; }; export const clip: RouteHandlerMethod = async (request, reply) => { const validationMessages = validateRequestBody(request.body as ClipArgs); if (validationMessages.length > 0) { reply .status(constants.HTTP_STATUS_BAD_REQUEST) .send({ errors: validationMessages, }); return; } const videoType = getVideoType((request.body as ClipArgs).url as string); if (videoType === null) { reply .status(constants.HTTP_STATUS_UNPROCESSABLE_ENTITY) .send({ errors: ['Unsupported URL.'], }); } const clipper = createVideoClipper({ type: videoType as VideoType, downloaderExecutablePath: process.env.YOUTUBE_DOWNLOADER_EXECUTABLE_PATH as string, }); const { url, start, end } = request.body as ClipVideoParams; const clipResult = await clipper({ url, start, end, }); reply .header('Content-Type', clipResult.contentType) .send(clipResult.content); };