vendor/pimcore/pimcore/models/Asset/Video/Thumbnail/Processor.php line 176

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Model\Asset\Video\Thumbnail;
  15. use Pimcore\File;
  16. use Pimcore\Logger;
  17. use Pimcore\Messenger\VideoConvertMessage;
  18. use Pimcore\Model;
  19. use Pimcore\Model\Tool\TmpStore;
  20. use Pimcore\Tool\Storage;
  21. use Symfony\Component\Lock\LockFactory;
  22. use Symfony\Component\Messenger\MessageBusInterface;
  23. /**
  24.  * @internal
  25.  */
  26. class Processor
  27. {
  28.     /**
  29.      * @var array
  30.      */
  31.     protected static $argumentMapping = [
  32.         'resize' => ['width''height'],
  33.         'scaleByWidth' => ['width'],
  34.         'scaleByHeight' => ['height'],
  35.     ];
  36.     /**
  37.      * @var \Pimcore\Video\Adapter[]
  38.      */
  39.     protected $queue = [];
  40.     /**
  41.      * @var string
  42.      */
  43.     protected $processId;
  44.     /**
  45.      * @var int
  46.      */
  47.     protected $assetId;
  48.     /**
  49.      * @var Config
  50.      */
  51.     protected $config;
  52.     /**
  53.      * @var int
  54.      */
  55.     protected $status;
  56.     /**
  57.      * @var null|string
  58.      */
  59.     protected $deleteSourceAfterFinished;
  60.     /**
  61.      * @param Model\Asset\Video $asset
  62.      * @param Config $config
  63.      * @param array $onlyFormats
  64.      *
  65.      * @return Processor|null
  66.      *
  67.      * @throws \Exception
  68.      */
  69.     public static function process(Model\Asset\Video $asset$config$onlyFormats = [])
  70.     {
  71.         if (!\Pimcore\Video::isAvailable()) {
  72.             throw new \Exception('No ffmpeg executable found, please configure the correct path in the system settings');
  73.         }
  74.         $storage Storage::get('thumbnail');
  75.         $instance = new self();
  76.         $formats = empty($onlyFormats) ? ['mp4'] : $onlyFormats;
  77.         $instance->setProcessId(uniqid());
  78.         $instance->setAssetId($asset->getId());
  79.         $instance->setConfig($config);
  80.         //create dash file(.mpd), if medias exists
  81.         $medias $config->getMedias();
  82.         if (count($medias) > 0) {
  83.             $formats[] = 'mpd';
  84.         }
  85.         // check for running or already created thumbnails
  86.         $customSetting $asset->getCustomSetting('thumbnails');
  87.         $existingFormats = [];
  88.         if (is_array($customSetting) && array_key_exists($config->getName(), $customSetting)) {
  89.             if ($customSetting[$config->getName()]['status'] == 'inprogress') {
  90.                 if (TmpStore::get($instance->getJobStoreId($customSetting[$config->getName()]['processId']))) {
  91.                     return null;
  92.                 }
  93.             } elseif ($customSetting[$config->getName()]['status'] == 'finished') {
  94.                 // check if the files are there
  95.                 $formatsToConvert = [];
  96.                 foreach ($formats as $f) {
  97.                     $format $customSetting[$config->getName()]['formats'][$f] ?? null;
  98.                     if (!$storage->fileExists($asset->getRealPath() . $format)) {
  99.                         $formatsToConvert[] = $f;
  100.                     } else {
  101.                         $existingFormats[$f] = $customSetting[$config->getName()]['formats'][$f];
  102.                     }
  103.                 }
  104.                 if (!empty($formatsToConvert)) {
  105.                     $formats $formatsToConvert;
  106.                 } else {
  107.                     return null;
  108.                 }
  109.             } elseif ($customSetting[$config->getName()]['status'] == 'error') {
  110.                 throw new \Exception('Unable to convert video, see logs for details.');
  111.             }
  112.         }
  113.         //generate tmp file only for new jobs
  114.         $sourceFile $asset->getTemporaryFile(true);
  115.         $instance->setDeleteSourceAfterFinished($sourceFile);
  116.         foreach ($formats as $format) {
  117.             $thumbDir $asset->getRealPath() . '/video-thumb__' $asset->getId() . '__' $config->getName();
  118.             $filename preg_replace("/\." preg_quote(File::getFileExtension($asset->getFilename()), '/') . '/'''$asset->getFilename()) . '.' $format;
  119.             $storagePath $thumbDir '/' $filename;
  120.             $tmpPath File::getLocalTempFilePath($format);
  121.             $converter \Pimcore\Video::getInstance();
  122.             $converter->load($sourceFile, ['asset' => $asset]);
  123.             $converter->setAudioBitrate($config->getAudioBitrate());
  124.             $converter->setVideoBitrate($config->getVideoBitrate());
  125.             $converter->setFormat($format);
  126.             $converter->setDestinationFile($tmpPath);
  127.             $converter->setStorageFile($storagePath);
  128.             //add media queries for mpd file generation
  129.             if ($format == 'mpd') {
  130.                 $medias $config->getMedias();
  131.                 foreach ($medias as $media => $transformations) {
  132.                     //used just to generate arguments for medias
  133.                     $subConverter \Pimcore\Video::getInstance();
  134.                     self::applyTransformations($subConverter$transformations);
  135.                     $medias[$media]['converter'] = $subConverter;
  136.                 }
  137.                 $converter->setMedias($medias);
  138.             }
  139.             $transformations $config->getItems();
  140.             self::applyTransformations($converter$transformations);
  141.             $instance->queue[] = $converter;
  142.         }
  143.         $customSetting $asset->getCustomSetting('thumbnails');
  144.         $customSetting is_array($customSetting) ? $customSetting : [];
  145.         $customSetting[$config->getName()] = [
  146.             'status' => 'inprogress',
  147.             'formats' => $existingFormats,
  148.             'processId' => $instance->getProcessId(),
  149.         ];
  150.         $asset->setCustomSetting('thumbnails'$customSetting);
  151.         Model\Version::disable();
  152.         $asset->save();
  153.         Model\Version::enable();
  154.         $instance->save();
  155.         \Pimcore::getContainer()->get(MessageBusInterface::class)->dispatch(
  156.             new VideoConvertMessage($instance->getProcessId())
  157.         );
  158.         return $instance;
  159.     }
  160.     private static function applyTransformations($converter$transformations)
  161.     {
  162.         if (is_array($transformations) && count($transformations) > 0) {
  163.             foreach ($transformations as $transformation) {
  164.                 if (!empty($transformation)) {
  165.                     $arguments = [];
  166.                     $mapping self::$argumentMapping[$transformation['method']];
  167.                     if (is_array($transformation['arguments'])) {
  168.                         foreach ($transformation['arguments'] as $key => $value) {
  169.                             $position array_search($key$mapping);
  170.                             if ($position !== false) {
  171.                                 $arguments[$position] = $value;
  172.                             }
  173.                         }
  174.                     }
  175.                     ksort($arguments);
  176.                     if (count($mapping) == count($arguments)) {
  177.                         call_user_func_array([$converter$transformation['method']], $arguments);
  178.                     } else {
  179.                         $message 'Video Transform failed: cannot call method `' $transformation['method'] . '´ with arguments `' implode(','$arguments) . '´ because there are too few arguments';
  180.                         Logger::error($message);
  181.                     }
  182.                 }
  183.             }
  184.         }
  185.     }
  186.     /**
  187.      * @param string $processId
  188.      */
  189.     public static function execute($processId)
  190.     {
  191.         $instance = new self();
  192.         $instance->setProcessId($processId);
  193.         $instanceItem TmpStore::get($instance->getJobStoreId($processId));
  194.         /**
  195.          * @var self $instance
  196.          */
  197.         $instance $instanceItem->getData();
  198.         $formats = [];
  199.         $conversionStatus 'finished';
  200.         // check if there is already a transcoding process running, wait if so ...
  201.         $lock \Pimcore::getContainer()->get(LockFactory::class)->createLock('video-transcoding'7200);
  202.         $lock->acquire(true);
  203.         $asset Model\Asset::getById($instance->getAssetId());
  204.         // start converting
  205.         foreach ($instance->queue as $converter) {
  206.             try {
  207.                 Logger::info('start video ' $converter->getFormat() . ' to ' $converter->getDestinationFile());
  208.                 $success $converter->save();
  209.                 Logger::info('finished video ' $converter->getFormat() . ' to ' $converter->getDestinationFile());
  210.                 $source fopen($converter->getDestinationFile(), 'rb');
  211.                 Storage::get('thumbnail')->writeStream($converter->getStorageFile(), $source);
  212.                 fclose($source);
  213.                 unlink($converter->getDestinationFile());
  214.                 if ($converter->getFormat() === 'mpd') {
  215.                     $streamFilesPath str_replace('.mpd''-stream*.mp4'$converter->getDestinationFile());
  216.                     $streams glob($streamFilesPath);
  217.                     $parentPath dirname($converter->getStorageFile());
  218.                     foreach ($streams as $steam) {
  219.                         $storagePath $parentPath '/' basename($steam);
  220.                         $source fopen($steam'rb');
  221.                         Storage::get('thumbnail')->writeStream($storagePath$source);
  222.                         fclose($source);
  223.                         unlink($steam);
  224.                         // set proper permissions
  225.                         @chmod($storagePathFile::getDefaultMode());
  226.                     }
  227.                 }
  228.                 if ($success) {
  229.                     $formats[$converter->getFormat()] =  preg_replace('/' preg_quote($asset->getRealPath(), '/') . '/'''$converter->getStorageFile(), 1);
  230.                 } else {
  231.                     $conversionStatus 'error';
  232.                 }
  233.                 $converter->destroy();
  234.             } catch (\Exception $e) {
  235.                 Logger::error($e);
  236.             }
  237.         }
  238.         $lock->release();
  239.         if ($asset) {
  240.             $customSetting $asset->getCustomSetting('thumbnails');
  241.             $customSetting is_array($customSetting) ? $customSetting : [];
  242.             if (array_key_exists($instance->getConfig()->getName(), $customSetting)
  243.                 && array_key_exists('formats'$customSetting[$instance->getConfig()->getName()])
  244.                 && is_array($customSetting[$instance->getConfig()->getName()]['formats'])) {
  245.                 $formats array_merge($customSetting[$instance->getConfig()->getName()]['formats'], $formats);
  246.             }
  247.             $customSetting[$instance->getConfig()->getName()] = [
  248.                 'status' => $conversionStatus,
  249.                 'formats' => $formats,
  250.             ];
  251.             $asset->setCustomSetting('thumbnails'$customSetting);
  252.             Model\Version::disable();
  253.             $asset->save();
  254.             Model\Version::enable();
  255.         }
  256.         if ($instance->getDeleteSourceAfterFinished()) {
  257.             @unlink($instance->getDeleteSourceAfterFinished());
  258.         }
  259.         TmpStore::delete($instance->getJobStoreId());
  260.     }
  261.     /**
  262.      * @return string|null
  263.      */
  264.     public function getDeleteSourceAfterFinished(): ?string
  265.     {
  266.         return $this->deleteSourceAfterFinished;
  267.     }
  268.     /**
  269.      * @param string|null $deleteSourceAfterFinished
  270.      */
  271.     public function setDeleteSourceAfterFinished(?string $deleteSourceAfterFinished): void
  272.     {
  273.         $this->deleteSourceAfterFinished $deleteSourceAfterFinished;
  274.     }
  275.     /**
  276.      * @return bool
  277.      */
  278.     public function save()
  279.     {
  280.         TmpStore::add($this->getJobStoreId(), $this'video-job');
  281.         return true;
  282.     }
  283.     /**
  284.      * @param string $processId
  285.      *
  286.      * @return string
  287.      */
  288.     protected function getJobStoreId($processId null)
  289.     {
  290.         if (!$processId) {
  291.             $processId $this->getProcessId();
  292.         }
  293.         return 'video-job-' $processId;
  294.     }
  295.     /**
  296.      * @param string $processId
  297.      *
  298.      * @return $this
  299.      */
  300.     public function setProcessId($processId)
  301.     {
  302.         $this->processId $processId;
  303.         return $this;
  304.     }
  305.     /**
  306.      * @return string
  307.      */
  308.     public function getProcessId()
  309.     {
  310.         return $this->processId;
  311.     }
  312.     /**
  313.      * @param int $assetId
  314.      *
  315.      * @return $this
  316.      */
  317.     public function setAssetId($assetId)
  318.     {
  319.         $this->assetId $assetId;
  320.         return $this;
  321.     }
  322.     /**
  323.      * @return int
  324.      */
  325.     public function getAssetId()
  326.     {
  327.         return $this->assetId;
  328.     }
  329.     /**
  330.      * @param Config $config
  331.      *
  332.      * @return $this
  333.      */
  334.     public function setConfig($config)
  335.     {
  336.         $this->config $config;
  337.         return $this;
  338.     }
  339.     /**
  340.      * @return Config
  341.      */
  342.     public function getConfig()
  343.     {
  344.         return $this->config;
  345.     }
  346.     /**
  347.      * @param array $queue
  348.      *
  349.      * @return $this
  350.      */
  351.     public function setQueue($queue)
  352.     {
  353.         $this->queue $queue;
  354.         return $this;
  355.     }
  356.     /**
  357.      * @return array
  358.      */
  359.     public function getQueue()
  360.     {
  361.         return $this->queue;
  362.     }
  363. }