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

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