|
- import {
- createVideoClipper,
- VideoType,
- CreateVideoClipperParams,
- } 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)) {
- messages.push('Invalid end value.');
- } else if (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)) {
- messages.push('Invalid end value.');
- } else if (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 VideoType.YOUTUBE;
- }
-
- 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({
- message: 'Unsupported URL.',
- });
- }
-
- const { url, start, end } = request.body as ClipArgs;
- const videoClipperArgs = {
- type: videoType,
- url,
- start,
- end,
- downloaderExecutablePath: process.env.YOUTUBE_DOWNLOADER_EXECUTABLE_PATH,
- } as CreateVideoClipperParams;
- const clipper = createVideoClipper(videoClipperArgs);
- clipper.on('process', (arg: Record<string, unknown>) => {
- request.server.log.info(`${arg.type as string}:${arg.phase as string}`);
- if (typeof arg.command === 'string') {
- request.server.log.debug(`> ${arg.command}`);
- }
- });
-
- let clipResult: Record<string, unknown>;
- clipper.on('success', (result: Record<string, unknown>) => {
- clipResult = result;
- });
-
- let theError: Error;
- clipper.on('error', (error: Error) => {
- theError = error;
- });
-
- clipper.on('end', () => {
- if (theError) {
- reply
- .status(constants.HTTP_STATUS_INTERNAL_SERVER_ERROR)
- .send({
- message: theError.message,
- });
- return;
- }
-
- reply
- .header('Content-Type', clipResult.type as string)
- .send(clipResult.output as Buffer);
- });
-
- clipper.process();
- };
|