src/Controller/ApiController.php line 46

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\DTO\CropVideoDTO;
  4. use App\DTO\MergeVideoDTO;
  5. use App\Entity\UserAgent;
  6. use App\Exception\AppException;
  7. use App\Service\ConverterServiceInterface;
  8. use App\Service\FileService;
  9. use App\Service\FileServiceInterface;
  10. use App\Service\VideoProcessServiceInterface;
  11. use Doctrine\ORM\EntityManagerInterface;
  12. use Psr\Log\LoggerInterface;
  13. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  14. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  15. use Symfony\Component\Finder\Exception\AccessDeniedException;
  16. use Symfony\Component\HttpFoundation\File\UploadedFile;
  17. use Symfony\Component\HttpFoundation\JsonResponse;
  18. use Symfony\Component\HttpFoundation\Request;
  19. use Symfony\Component\HttpFoundation\Response;
  20. use Symfony\Component\Routing\Annotation\Route;
  21. use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
  22. use Symfony\Component\Serializer\SerializerInterface;
  23. use Symfony\Contracts\HttpClient\HttpClientInterface;
  24. class ApiController extends AbstractController
  25. {
  26.     public function __construct(
  27.         private string $uploadDir,
  28.         private FileServiceInterface $fileService,
  29.         private ConverterServiceInterface $converter,
  30.         private VideoProcessServiceInterface $videoProcess,
  31.         private LoggerInterface $interviewPublishStreamLogger,
  32.         private LoggerInterface $logger,
  33.         private HttpClientInterface $client,
  34.         private SerializerInterface $serializer,
  35.     ) {
  36.     }
  37.     /**
  38.      * @Route("/", name="homepage")
  39.      * @return Response
  40.      */
  41.     public function index()
  42.     {
  43.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  44.         return $this->render('home.html.twig', []);
  45.     }
  46.     /**
  47.      * Used by Flashphoner server in process of allowing media session
  48.      *
  49.      * @Route("/rest/defaultHigher/connect", name="rest_application_verify", methods={"POST"})
  50.      */
  51.     public function appVerify(Request $request)
  52.     {
  53.         $content $request->getContent();
  54.         $this->denyAccessUnlessGranted('stream'$request);
  55.         return new Response($content200);
  56.     }
  57.     /**
  58.      * @Route("/rest/defaultHigher/publishStream", name="rest_publish_stream", methods={"POST"})
  59.      */
  60.     public function publishStream(Request $requestEntityManagerInterface $entityManager)
  61.     {
  62.         $content json_decode($request->getContent(), true);
  63.         $filename 'stream-' $content['name'] . '-' $content['mediaSessionId'];
  64.         $userAgentObject = new UserAgent();
  65.         $userAgentObject->setFilename($filename);
  66.         if (!empty($content['custom']['userAgent'])) {
  67.             $userAgentObject->setUserAgentData($content['custom']['userAgent']);
  68.         } else {
  69.             $userAgentObject->setUserAgentData('-');
  70.         }
  71.         $entityManager->persist($userAgentObject);
  72.         $entityManager->flush();
  73.         return new JsonResponse($content200);
  74.     }
  75.     /**
  76.      * @Route("/rest/defaultHigher/ConnectionStatusEvent", name="rest_higher_connection_status_event", methods={"POST"})
  77.      */
  78.     public function onHigherConnectionStatusEvent(Request $request)
  79.     {
  80.         return new Response($request->getContent(), 200);
  81.     }
  82.     /**
  83.      * Used by Flashphoner upon finish video stream recording.
  84.      *
  85.      * @Route("/rest/defaultHigher/convert/{filename}", name="rest_convert_video", methods={"GET"})
  86.      */
  87.     public function convertVideo($filename)
  88.     {
  89.         $filename $this->converter->convertToMp4($filename);
  90.         $this->videoProcess->addWatermark($filename);
  91.         $this->videoProcess->createSnapshot($filename);
  92.         $this->videoProcess->saveToDatabaseVideoFileByName($filename);
  93.         return new Response($filename);
  94.     }
  95.     /**
  96.      * Used by Flashphoner upon finish video interview recording.
  97.      *
  98.      * @Route("/rest/defaultHigher/convertInterview/{filename}", name="rest_convert_video_interview", methods={"GET"})
  99.      */
  100.     public function convertInterviewVideo($filename)
  101.     {
  102.         $filename $this->converter->convertToMp4($filename);
  103.         $this->videoProcess->addWatermark($filenametrue);
  104.         $this->videoProcess->createSnapshot($filename);
  105.         $this->videoProcess->saveToDatabaseVideoFileByName($filename);
  106.         return new Response($filename);
  107.     }
  108.     /**
  109.      * Only for use of Frontend application.
  110.      *
  111.      * @Route("/rest/defaultHigher/path", name="media_server_folder", methods={"GET"})
  112.      */
  113.     public function getCurrentMediaPath()
  114.     {
  115.         return new Response($_ENV['MS_URL'] . '/' $_ENV['RECORDS_DIR']);
  116.     }
  117.     /**
  118.      * Used by frontend application to upload files
  119.      *
  120.      * @Route("/rest/defaultHigher/upload", name="video_file_upload", methods={"POST"})
  121.      */
  122.     public function uploadVideo(Request $request)
  123.     {
  124.         $this->denyAccessUnlessGranted('upload'$request);
  125.         /** @var UploadedFile $file */
  126.         $file $request->files->get('nativeCam');
  127.         if (null === $file) {
  128.             throw new AppException('Missing video file in the request.'400);
  129.         }
  130.         $filename $this->fileService->createVideoFile($file);
  131.         $filename $this->converter->convertToMp4($filename);
  132.         $filename $this->videoProcess->resizeVideoIfLarge($filenametrue);
  133.         if ($oldFilename $this->videoProcess->getVideoFilenameByHash($filename)) {
  134.             unlink($this->uploadDir $filename);
  135.             unlink($this->uploadDir 'small_' $filename);
  136.             return new Response($_ENV['MS_URL'] . '/' $_ENV['RECORDS_DIR'] . $oldFilename);
  137.         }
  138.         $this->videoProcess->createSnapshot($filename);
  139.         $this->videoProcess->saveToDatabaseVideoFileByName($filename);
  140.         $this->videoProcess->generateHashForVideo($filename);
  141.         return new Response($_ENV['MS_URL'] . '/' $_ENV['RECORDS_DIR'] . $filename);
  142.     }
  143.     /**
  144.      * Used by candidate application to upload camera files
  145.      *
  146.      * @Route("/rest/defaultHigher/uploadCamera", name="camera_file_upload", methods={"POST"})
  147.      */
  148.     public function uploadCameraVideo(Request $requestEntityManagerInterface $entityManager)
  149.     {
  150.         $this->denyAccessUnlessGranted('upload'$request);
  151.         /** @var UploadedFile $file */
  152.         $file $request->files->get('nativeCam');
  153.         if (null === $file) {
  154.             throw new AppException('Missing video file in the request.'400);
  155.         }
  156.         $filename $this->fileService->createVideoFile($filetrue);
  157.         $this->videoProcess->checkVideoLength($filename33.00);
  158.         $filename $this->converter->convertToMp4($filename);
  159.         $filename $this->videoProcess->resizeVideoIfLarge($filename);
  160.         $this->videoProcess->addWatermark($filename);
  161.         $this->videoProcess->createSnapshot($filename);
  162.         $userAgentData $request->headers->get('User-Agent');
  163.         $userAgentObject = new UserAgent();
  164.         $userAgentObject->setFilename(FileService::filenameWithoutExtension($filename));
  165.         $userAgentObject->setUserAgentData($userAgentData);
  166.         $entityManager->persist($userAgentObject);
  167.         $entityManager->flush();
  168.         $this->videoProcess->saveToDatabaseVideoFileByName($filename);
  169.         return new Response($_ENV['MS_URL'] . '/' $_ENV['RECORDS_DIR'] . $filename);
  170.     }
  171.     /**
  172.      * Used by customer to merge streamed camera video and uploaded video
  173.      *
  174.      * @Route("/rest/defaultHigher/merge", name="merge_video_files", methods={"GET"})
  175.      */
  176.     #[Route(path'/rest/defaultHigher/marge'name'merge_video_files'methods: ['GET'])]
  177.     #[ParamConverter('mergeVideos'MergeVideoDTO::class)]
  178.     public function mergeStreamAndUploadVideo(Request $requestMergeVideoDTO $mergeVideos): Response
  179.     {
  180.         $this->denyAccessUnlessGranted('upload'$request);
  181.         $filename $this->videoProcess->mergeVideos($mergeVideos);
  182.         $this->videoProcess->createSnapshot($filename);
  183.         return new Response($_ENV['MS_URL'] . '/' $_ENV['RECORDS_DIR'] . $filename);
  184.     }
  185.     /**
  186.      * Used only by frontend application on action 'Re-record'
  187.      *
  188.      * @Route("/rest/defaultHigher/delete/{filename}", name="api_delete_video_file", methods={"DELETE"})
  189.      */
  190.     public function delete(string $filename)
  191.     {
  192.         $filenameNoExt FileService::filenameWithoutExtension($filename);
  193.         try {
  194.             if (!file_exists($_ENV['RECORDS_DIR_PATH'].$filename)) {
  195.                 $this->videoProcess->remove($filenameNoExt);
  196.                 return new JsonResponse(['message' => 'Video removed.']);
  197.             }
  198.             
  199.             //copy($_ENV['RECORDS_DIR_PATH'].$filename, $_ENV['RECORDS_DIR_PATH'].'trash/'.$filename);
  200.             unlink($_ENV['RECORDS_DIR_PATH'].$filename);
  201.             unlink($_ENV['RECORDS_DIR_PATH'].$filenameNoExt.'.jpg');
  202.         } catch (\Exception $ex) {
  203.             $this->logger->error(sprintf('Problem in delete file: "%s", message: %s'$filename$ex->getMessage()));
  204.         }
  205.         $this->videoProcess->remove($filenameNoExt);
  206.         return new JsonResponse(['message' => 'Video removed.']);
  207.     }
  208.     /**
  209.      * Used by API command to check if video file in database exists physically
  210.      * @Route("/rest/defaultHigher/check/{filename}", name="api_check_video_file", methods={"GET"})
  211.      */
  212.     public function checkIfVideoPhysicallyExist(string $filename): Response
  213.     {
  214.         if (file_exists($this->uploadDir $filename)) {
  215.             return new Response('OK'200);
  216.         }
  217.         return new Response('Not found'404);
  218.     }
  219.     /**
  220.      * @Route("/rest/defaultHigher/checkList", name="api_check_list_video_files", methods={"POST"})
  221.      */
  222.     public function checkListOfVideoFiles(Request $request): Response
  223.     {
  224.         $this->denyAccessUnlessGranted('interview-list'$request);
  225.         
  226.         $files json_decode($request->getContent(), true);
  227.         if (empty($files)) {
  228.             return new JsonResponse([]);
  229.         }
  230.         $existingFiles = [];
  231.         foreach($files as $file) {
  232.             if (file_exists($this->uploadDir.$file)) {
  233.                 $existingFiles[] = $file;
  234.             }
  235.         }
  236.         return new JsonResponse($existingFiles);
  237.     }
  238.     /**
  239.      * @Route("/rest/defaultHigher/changePoster", name="api_change_video_poster", methods={"POST"})
  240.      */
  241.     public function changeVideoPoster(Request $request)
  242.     {
  243.         $this->denyAccessUnlessGranted('upload'$request);
  244.         /** @var UploadedFile $file */
  245.         $file $request->files->get('posterImage');
  246.         if (null === $file) {
  247.             throw new AppException('Missing image file in the request.'400);
  248.         }
  249.         if (!in_array($file->getClientOriginalExtension(), ['jpg''jpeg'])) {
  250.             throw new AppException('Image format invalid.'400);
  251.         }
  252.         $videoFilename $request->get('video');
  253.         if (!file_exists($_ENV['RECORDS_DIR_PATH'] . $videoFilename)) {
  254.             throw new AppException('Video file does not exists.'400);
  255.         }
  256.         $filename explode('.'$videoFilename)[0] . '.jpg';
  257.         try {
  258.             $file->move($_ENV['RECORDS_DIR_PATH'], $filename);
  259.         } catch (\Exception $ex) {
  260.             $this->logger->error($ex->getMessage());
  261.             throw new AppException('Error saving poster image.'500);
  262.         }
  263.         return new Response('OK'200);
  264.     }
  265.     /**
  266.      * Used by candidate application to upload camera files
  267.      *
  268.      * @Route("/rest/defaultHigher/uploadWbVideo", name="wb_file_upload", methods={"POST"})
  269.      */
  270.     public function uploadWbVideo(Request $requestEntityManagerInterface $entityManager)
  271.     {
  272.         $captchaToken $request->request->get('captcha');
  273.         $response $this->client->request('POST''https://www.google.com/recaptcha/api/siteverify?secret='
  274.             .$_ENV['RECAPTCHA_SECRET_KEY']. '&response=' $captchaToken);
  275.         $arrResponse \json_decode($response->getContent(), true512JSON_THROW_ON_ERROR);
  276.         if (!$arrResponse['success']) {
  277.             throw new AccessDeniedException('Invalid re-captcha token.'401);
  278.         }
  279.         /** @var UploadedFile $file */
  280.         $file $request->files->get('nativeCam');
  281.         if (null === $file) {
  282.             throw new AppException('Missing video file in the request.'400);
  283.         }
  284.         $filename $this->fileService->createWbVideoFile($file);
  285.         $this->videoProcess->checkVideoLength($filename185.00);
  286.         $filename $this->converter->convertToMp4($filename);
  287.         $filename $this->videoProcess->resizeVideoIfLarge($filename);
  288.         return new Response($_ENV['MS_URL'] . '/' $_ENV['RECORDS_DIR'] . $filename);
  289.     }
  290.     /**
  291.      * Used by customer to crop video file for use in social networks
  292.      *
  293.      * @Route("/rest/defaultHigher/cropVideo", name="crop_video", methods={"POST"})
  294.      */
  295.     public function cropVideoForSocialNetworks(Request $request)
  296.     {
  297.         $this->denyAccessUnlessGranted('upload'$request);
  298.         $cropVideoDTO $this->serializer->deserialize($request->getContent(), CropVideoDTO::class, 'json', [
  299.             AbstractNormalizer::ATTRIBUTES => ['videoUrl''marginH''marginV'],
  300.             AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
  301.         ]);
  302.         return new Response($_ENV['MS_URL'] . '/' $_ENV['RECORDS_DIR'] . $this->videoProcess
  303.                 ->cropVideoFileForSocialNetworks($cropVideoDTO));
  304.     }
  305. }