Files
esun3d/app/code/Esun/Video/Controller/Adminhtml/Upload/Chunk.php
T
2026-09-15 10:35:56 +08:00

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),
];
}
}