From a6ef98210c979583b47260dfcf77182c13101add Mon Sep 17 00:00:00 2001 From: flashwave Date: Sat, 30 May 2020 23:49:59 +0000 Subject: [PATCH] Added thumbnail support for videos and embedded covers in audio. --- public/index.php | 46 +++++-------------------------- src/Upload.php | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 39 deletions(-) diff --git a/public/index.php b/public/index.php index fa81209..bee0796 100644 --- a/public/index.php +++ b/public/index.php @@ -132,6 +132,7 @@ if(preg_match('#^/uploads/([a-zA-Z0-9-_]{32})(\.t)?/?$#', $reqPath, $matches)) { $uploadInfo->bumpExpiry(); } + $fileName = $uploadInfo->getName(); $contentType = $uploadInfo->getType(); if($contentType === 'application/octet-stream' || substr($contentType, 0, 5) === 'text/') @@ -139,49 +140,16 @@ if(preg_match('#^/uploads/([a-zA-Z0-9-_]{32})(\.t)?/?$#', $reqPath, $matches)) { $sourceDir = basename($getThumbnail ? PRM_THUMBS : PRM_UPLOADS); - if($getThumbnail) { - if(substr($contentType, 0, 6) !== 'image/') { - http_response_code(404); - echo 'Thumbnails are not supported for this filetype.'; - return; - } - - try { - $imagick = new \Imagick($uploadInfo->getPath()); - $imagick->setImageFormat('jpg'); - $imagick->setImageCompressionQuality(40); - - $thumbRes = 100; - $width = $imagick->getImageWidth(); - $height = $imagick->getImageHeight(); - - if ($width > $height) { - $resizeWidth = $width * $thumbRes / $height; - $resizeHeight = $thumbRes; - } else { - $resizeWidth = $thumbRes; - $resizeHeight = $height * $thumbRes / $width; - } - - $imagick->resizeImage( - $resizeWidth, $resizeHeight, - \Imagick::FILTER_GAUSSIAN, 0.7 - ); - - $imagick->cropImage( - $thumbRes, - $thumbRes, - ($resizeWidth - $thumbRes) / 2, - ($resizeHeight - $thumbRes) / 2 - ); - - $imagick->writeImage($uploadInfo->getThumbPath()); - } catch(\Exception $ex) {} + if($getThumbnail && $uploadInfo->supportsThumbnail()) { + if(!is_file($uploadInfo->getThumbPath())) + $uploadInfo->createThumbnail(); + $contentType = 'image/jpeg'; + $fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg'; } header(sprintf('X-Accel-Redirect: /%s/%s', $sourceDir, $uploadInfo->getId())); header(sprintf('Content-Type: %s', $contentType)); - header(sprintf('Content-Disposition: inline; filename="%s"', addslashes($uploadInfo->getName()))); + header(sprintf('Content-Disposition: inline; filename="%s"', addslashes($fileName))); return; } diff --git a/src/Upload.php b/src/Upload.php index f6a90c4..b2b41ee 100644 --- a/src/Upload.php +++ b/src/Upload.php @@ -2,6 +2,7 @@ namespace EEPROM; use Exception; +use Imagick; use JsonSerializable; class UploadNotFoundException extends Exception {}; @@ -70,6 +71,15 @@ final class Upload implements JsonSerializable { public function getType(): string { return $this->upload_type ?? 'text/plain'; } + public function isImage(): bool { + return substr($this->getType(), 0, 6) === 'image/'; + } + public function isVideo(): bool { + return substr($this->getType(), 0, 6) === 'video/'; + } + public function isAudio(): bool { + return substr($this->getType(), 0, 6) === 'audio/'; + } public function getName(): string { return $this->upload_name ?? ''; @@ -387,4 +397,66 @@ final class Upload implements JsonSerializable { return $out; } + + public function supportsThumbnail(): bool { + return $this->isImage() || $this->isAudio() || $this->isVideo(); + } + + public function createThumbnail(): void { + $tmpFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'eeprom' . bin2hex(random_bytes(10)) . '.jpg'; + + try { + if($this->isImage()) + $imagick = new Imagick($this->getPath()); + elseif($this->isAudio()) + $imagick = $this->getCoverFromAudio($tmpFile); + elseif($this->isVideo()) + $imagick = $this->getFrameFromVideo($tmpFile); + + if(!isset($imagick)) + return; + + $imagick->setImageFormat('jpg'); + $imagick->setImageCompressionQuality(40); + + $thumbRes = 100; + $width = $imagick->getImageWidth(); + $height = $imagick->getImageHeight(); + + if ($width > $height) { + $resizeWidth = $width * $thumbRes / $height; + $resizeHeight = $thumbRes; + } else { + $resizeWidth = $thumbRes; + $resizeHeight = $height * $thumbRes / $width; + } + + $imagick->resizeImage( + $resizeWidth, $resizeHeight, + Imagick::FILTER_GAUSSIAN, 0.7 + ); + + $imagick->cropImage( + $thumbRes, + $thumbRes, + ($resizeWidth - $thumbRes) / 2, + ($resizeHeight - $thumbRes) / 2 + ); + + $imagick->writeImage($this->getThumbPath()); + } catch(Exception $ex) {} + + if(is_file($tmpFile)) + unlink($tmpFile); + } + + private function getFrameFromVideo(string $path): Imagick { + shell_exec(sprintf('ffmpeg -i %s -ss 00:00:01.000 -vframes 1 %s', $this->getPath(), $path)); + return new Imagick($path); + } + + private function getCoverFromAudio(string $path): Imagick { + shell_exec(sprintf('ffmpeg -i %s -an -vcodec copy %s', $this->getPath(), $path)); + return new Imagick($path); + } }