Clip Web videos.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

87 lines
2.1 KiB

  1. import {
  2. createVideoClipper,
  3. YouTube,
  4. VideoType,
  5. ClipVideoParams,
  6. } from '@modal/webvideo-clip-core';
  7. import { constants } from 'http2';
  8. import { RouteHandlerMethod } from 'fastify';
  9. const DURATION_STRING_REGEXP = /^\d\d:[0-5]\d:[0-5]\d(\.\d+)?$/;
  10. const validateRequestBody = (body: ClipVideoParams) => {
  11. const messages = [] as string[];
  12. const { url, start, end } = body;
  13. if (typeof url !== 'string') {
  14. messages.push('URL is required.');
  15. }
  16. const typeofStart = typeof start;
  17. if (typeofStart !== 'undefined') {
  18. if (
  19. !['string', 'number'].includes(typeofStart)
  20. || (typeofStart === 'string' && !DURATION_STRING_REGEXP.test(start as string))
  21. ) {
  22. messages.push('Invalid start value.');
  23. }
  24. }
  25. const typeofEnd = typeof end;
  26. if (typeofEnd !== 'undefined') {
  27. if (
  28. !['string', 'number'].includes(typeofEnd)
  29. || (typeofEnd === 'string' && !DURATION_STRING_REGEXP.test(end as string))
  30. ) {
  31. messages.push('Invalid end value.');
  32. }
  33. }
  34. return messages;
  35. };
  36. const getVideoType = (url: string) => {
  37. if (url.startsWith('https://www.youtube.com')) {
  38. return YouTube.VIDEO_TYPE as VideoType;
  39. }
  40. return null;
  41. };
  42. export const clip: RouteHandlerMethod = async (request, reply) => {
  43. const body = request.body as ClipVideoParams;
  44. const { url, start, end } = body;
  45. const validationMessages = validateRequestBody(body);
  46. if (validationMessages.length > 0) {
  47. reply
  48. .status(constants.HTTP_STATUS_BAD_REQUEST)
  49. .send({
  50. errors: validationMessages,
  51. });
  52. return;
  53. }
  54. const videoType = getVideoType(url);
  55. if (videoType === null) {
  56. reply
  57. .status(constants.HTTP_STATUS_UNPROCESSABLE_ENTITY)
  58. .send({
  59. errors: ['Unsupported URL.'],
  60. });
  61. }
  62. const clipper = createVideoClipper({
  63. type: videoType as VideoType,
  64. downloaderExecutablePath: process.env.VIDEO_DOWNLOADER_EXECUTABLE_PATH as string,
  65. });
  66. const clipResult = await clipper({
  67. url,
  68. start,
  69. end,
  70. });
  71. reply
  72. .header('Content-Type', clipResult.contentType)
  73. .send(clipResult.content);
  74. };