180 lines
8.4 KiB
PHP
180 lines
8.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Esun\Video\Controller\Adminhtml\Upload;
|
|
|
|
use Esun\Video\Model\Config;
|
|
use Esun\Video\Model\Ffmpeg;
|
|
use Esun\Video\Model\Ffprobe;
|
|
use Esun\Video\Model\VideoFactory;
|
|
use Magento\Backend\App\Action;
|
|
use Magento\Catalog\Model\Product\Media\Config as ProductMediaConfig;
|
|
use Magento\Framework\App\Filesystem\DirectoryList;
|
|
use Magento\Framework\Controller\Result\JsonFactory;
|
|
use Magento\Framework\Exception\LocalizedException;
|
|
use Magento\Framework\Filesystem;
|
|
use Magento\Framework\UrlInterface;
|
|
use Magento\MediaStorage\Model\File\UploaderFactory;
|
|
use Magento\Store\Model\StoreManagerInterface;
|
|
|
|
class Chunk extends Action
|
|
{
|
|
public const ADMIN_RESOURCE = 'Esun_Video::upload';
|
|
|
|
public function __construct(
|
|
Action\Context $context,
|
|
private readonly JsonFactory $jsonFactory,
|
|
private readonly Filesystem $filesystem,
|
|
private readonly Config $config,
|
|
private readonly Ffprobe $ffprobe,
|
|
private readonly Ffmpeg $ffmpeg,
|
|
private readonly VideoFactory $videoFactory,
|
|
private readonly UploaderFactory $uploaderFactory,
|
|
private readonly ProductMediaConfig $productMediaConfig,
|
|
private readonly StoreManagerInterface $storeManager
|
|
) {
|
|
parent::__construct($context);
|
|
}
|
|
|
|
public function execute()
|
|
{
|
|
$result = $this->jsonFactory->create();
|
|
try {
|
|
if (!$this->config->isEnabled()) {
|
|
throw new LocalizedException(__('Video upload is disabled.'));
|
|
}
|
|
$title = trim((string)$this->getRequest()->getParam('title'));
|
|
if ($title === '') {
|
|
throw new LocalizedException(__('Title is required.'));
|
|
}
|
|
|
|
$uploadId = preg_replace('/[^a-zA-Z0-9_-]/', '', (string)$this->getRequest()->getParam('upload_id'));
|
|
$index = (int)$this->getRequest()->getParam('index');
|
|
$total = (int)$this->getRequest()->getParam('total');
|
|
$name = basename((string)$this->getRequest()->getParam('name'));
|
|
if ($uploadId === '' || $index < 0 || $total < 1 || $index >= $total || strtolower(pathinfo($name, PATHINFO_EXTENSION)) !== 'mp4') {
|
|
throw new LocalizedException(__('Invalid MP4 upload request.'));
|
|
}
|
|
if (!isset($_FILES['chunk']) || (int)$_FILES['chunk']['error'] !== UPLOAD_ERR_OK) {
|
|
throw new LocalizedException(__('The video chunk was not uploaded.'));
|
|
}
|
|
|
|
$var = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
|
|
$chunkDir = 'esun_video/chunks/' . $uploadId;
|
|
$var->create($chunkDir);
|
|
$var->writeFile($chunkDir . '/' . $index . '.part', (string)file_get_contents($_FILES['chunk']['tmp_name']));
|
|
if ($index + 1 < $total) {
|
|
return $result->setData(['success' => true, 'completed' => false, 'next' => $index + 1]);
|
|
}
|
|
|
|
$assembled = 'esun_video/assembled/' . $uploadId . '.mp4';
|
|
$var->create(dirname($assembled));
|
|
$absolute = $var->getAbsolutePath($assembled);
|
|
$handle = fopen($absolute, 'wb');
|
|
if ($handle === false) {
|
|
throw new LocalizedException(__('Unable to assemble the uploaded video.'));
|
|
}
|
|
try {
|
|
for ($part = 0; $part < $total; $part++) {
|
|
$partPath = $chunkDir . '/' . $part . '.part';
|
|
if (!$var->isExist($partPath)) {
|
|
throw new LocalizedException(__('Video chunk %1 is missing.', $part));
|
|
}
|
|
$input = fopen($var->getAbsolutePath($partPath), 'rb');
|
|
if ($input === false) {
|
|
throw new LocalizedException(__('Unable to read video chunk %1.', $part));
|
|
}
|
|
stream_copy_to_stream($input, $handle);
|
|
fclose($input);
|
|
}
|
|
} finally {
|
|
fclose($handle);
|
|
}
|
|
|
|
$metadata = $this->ffprobe->inspect($absolute);
|
|
$cover = $this->saveCover($uploadId, $absolute);
|
|
$safeName = preg_replace('/[^a-zA-Z0-9_-]+/', '-', pathinfo($name, PATHINFO_FILENAME));
|
|
$videoPath = $this->config->getMediaPath() . '/videos/' . date('Y/m') . '/'
|
|
. trim((string)$safeName, '-') . '-' . substr($uploadId, 0, 12) . '.mp4';
|
|
$media = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
|
|
$media->create(dirname($videoPath));
|
|
$var->renameFile($assembled, $videoPath, $media);
|
|
|
|
$video = $this->videoFactory->create();
|
|
$video->setData([
|
|
'title' => $title,
|
|
'description' => trim((string)$this->getRequest()->getParam('description')),
|
|
'video_path' => $videoPath,
|
|
'cover_path' => $cover['permanent_path'],
|
|
'status' => $this->getRequest()->getParam('status', '1') === '0' ? 0 : 1,
|
|
'duration' => $metadata['duration'],
|
|
'size' => $metadata['size'],
|
|
'width' => $metadata['width'],
|
|
'height' => $metadata['height'],
|
|
'resolution' => $metadata['resolution'],
|
|
'original_name' => $name,
|
|
])->save();
|
|
$var->delete($chunkDir);
|
|
|
|
$mediaUrl = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA);
|
|
return $result->setData([
|
|
'success' => true,
|
|
'completed' => true,
|
|
'video' => [
|
|
'id' => (int)$video->getId(),
|
|
'title' => $title,
|
|
'description' => (string)$video->getData('description'),
|
|
'video_url' => $mediaUrl . ltrim($videoPath, '/'),
|
|
'cover_url' => $cover['url'],
|
|
'gallery_file' => $cover['gallery_file'],
|
|
'metadata' => $metadata,
|
|
'disabled' => (int)!$video->getData('status'),
|
|
],
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
return $result->setHttpResponseCode(400)->setData(['success' => false, 'message' => $e->getMessage()]);
|
|
}
|
|
}
|
|
|
|
private function saveCover(string $uploadId, string $absoluteVideo): array
|
|
{
|
|
if (isset($_FILES['cover']) && (int)$_FILES['cover']['error'] === UPLOAD_ERR_OK) {
|
|
$uploader = $this->uploaderFactory->create(['fileId' => 'cover']);
|
|
$uploader->setAllowedExtensions(['jpg', 'jpeg', 'png', 'gif']);
|
|
$uploader->setAllowRenameFiles(true);
|
|
$uploader->setFilesDispersion(true);
|
|
$media = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA);
|
|
$saved = $uploader->save($media->getAbsolutePath($this->productMediaConfig->getBaseTmpMediaPath()));
|
|
if (!is_array($saved) || empty($saved['file'])) {
|
|
throw new LocalizedException(__('Unable to save the cover image.'));
|
|
}
|
|
$galleryFile = (string)$saved['file'];
|
|
} else {
|
|
$galleryFile = '/e/s/esun-video-' . substr($uploadId, 0, 12) . '.jpg';
|
|
$tmpPath = $this->productMediaConfig->getTmpMediaPath($galleryFile);
|
|
$var = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
|
|
$generated = 'esun_video/generated_covers/' . $uploadId . '.jpg';
|
|
$var->create(dirname($generated));
|
|
$this->ffmpeg->createCover($absoluteVideo, $var->getAbsolutePath($generated));
|
|
$media = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
|
|
$media->create(dirname($tmpPath));
|
|
$media->writeFile($tmpPath, $var->readFile($generated));
|
|
$var->delete($generated);
|
|
}
|
|
|
|
$source = $this->productMediaConfig->getTmpMediaPath($galleryFile);
|
|
$extension = strtolower(pathinfo($galleryFile, PATHINFO_EXTENSION)) ?: 'jpg';
|
|
$permanent = $this->config->getMediaPath() . '/covers/' . date('Y/m') . '/'
|
|
. substr($uploadId, 0, 20) . '.' . $extension;
|
|
$mediaWrite = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
|
|
$mediaWrite->create(dirname($permanent));
|
|
$mediaWrite->writeFile($permanent, $mediaWrite->readFile($source));
|
|
|
|
return [
|
|
'gallery_file' => $galleryFile . '.tmp',
|
|
'permanent_path' => $permanent,
|
|
'url' => $this->productMediaConfig->getTmpMediaUrl($galleryFile),
|
|
];
|
|
}
|
|
}
|