From dcfe6a00ea4923df13ad638a5f42dbbebeb3cd36 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Mon, 19 May 2014 19:21:58 +0200 Subject: [PATCH] Refactored thumb generator; added imagick support --- src/Helpers/ThumbnailHelper.php | 165 ------------------ src/Models/Entities/PostEntity.php | 18 +- .../FlashThumbnailGenerator.php | 34 ++++ .../IThumbnailGenerator.php | 5 + .../ImageGdThumbnailGenerator.php | 89 ++++++++++ .../ImageImagickThumbnailGenerator.php | 69 ++++++++ .../ImageThumbnailGenerator.php | 15 ++ .../SmartThumbnailGenerator.php | 48 +++++ .../VideoThumbnailGenerator.php | 38 ++++ src/core.php | 2 +- 10 files changed, 314 insertions(+), 169 deletions(-) delete mode 100644 src/Helpers/ThumbnailHelper.php create mode 100644 src/ThumbnailGenerators/FlashThumbnailGenerator.php create mode 100644 src/ThumbnailGenerators/IThumbnailGenerator.php create mode 100644 src/ThumbnailGenerators/ImageGdThumbnailGenerator.php create mode 100644 src/ThumbnailGenerators/ImageImagickThumbnailGenerator.php create mode 100644 src/ThumbnailGenerators/ImageThumbnailGenerator.php create mode 100644 src/ThumbnailGenerators/SmartThumbnailGenerator.php create mode 100644 src/ThumbnailGenerators/VideoThumbnailGenerator.php diff --git a/src/Helpers/ThumbnailHelper.php b/src/Helpers/ThumbnailHelper.php deleted file mode 100644 index 53a38b36..00000000 --- a/src/Helpers/ThumbnailHelper.php +++ /dev/null @@ -1,165 +0,0 @@ - ($srcHeight / $srcWidth)) - { - $h = $srcHeight; - $w = $h * $dstWidth / $dstHeight; - } - else - { - $w = $srcWidth; - $h = $w * $dstHeight / $dstWidth; - } - $x = ($srcWidth - $w) / 2; - $y = ($srcHeight - $h) / 2; - - $dstImage = imagecreatetruecolor($dstWidth, $dstHeight); - imagecopyresampled($dstImage, $srcImage, 0, 0, $x, $y, $dstWidth, $dstHeight, $w, $h); - return $dstImage; - } - - public static function cropInside($srcImage, $dstWidth, $dstHeight) - { - $srcWidth = imagesx($srcImage); - $srcHeight = imagesy($srcImage); - - if (($dstHeight / $dstWidth) < ($srcHeight / $srcWidth)) - { - $h = $dstHeight; - $w = $h * $srcWidth / $srcHeight; - } - else - { - $w = $dstWidth; - $h = $w * $srcHeight / $srcWidth; - } - - $dstImage = imagecreatetruecolor($w, $h); - imagecopyresampled($dstImage, $srcImage, 0, 0, 0, 0, $w, $h, $srcWidth, $srcHeight); - return $dstImage; - } - - public static function generateFromUrl($url, $dstPath, $width, $height) - { - $tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg'; - - TransferHelper::download( - $url, - $tmpPath, - null); - - $ret = self::generateFromPath($tmpPath, $dstPath, $width, $height); - - unlink($tmpPath); - return $ret; - } - - public static function generateFromPath($srcPath, $dstPath, $width, $height) - { - $mime = mime_content_type($srcPath); - - switch ($mime) - { - case 'application/x-shockwave-flash': - $tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png'; - - $cmd = sprintf( - 'dump-gnash --screenshot last --screenshot-file "%s" -1 -r1 --max-advances 15 "%s"', - $tmpPath, - $srcPath); - exec($cmd); - - if (file_exists($tmpPath)) - { - $ret = self::generateFromPath($tmpPath, $dstPath, $width, $height); - unlink($tmpPath); - return $ret; - } - - exec('swfrender ' . $srcPath . ' -o ' . $tmpPath); - - if (file_exists($tmpPath)) - { - $ret = self::generateFromPath($tmpPath, $dstPath, $width, $height); - unlink($tmpPath); - return $ret; - } - - return false; - - case 'video/mp4': - case 'video/webm': - case 'video/ogg': - case 'application/ogg': - case 'video/x-flv': - case 'video/3gpp': - $tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg'; - - $cmd = sprintf( - 'ffmpegthumbnailer -i"%s" -o"%s" -s0 -t"12%%"', - $srcPath, - $tmpPath); - exec($cmd); - - if (file_exists($tmpPath)) - { - $ret = self::generateFromPath($tmpPath, $dstPath, $width, $height); - unlink($tmpPath); - return $ret; - } - - $cmd = sprintf( - 'ffmpeg -i "%s" -vframes 1 "%s"', - $srcPath, - $tmpPath); - exec($cmd); - - if (file_exists($tmpPath)) - { - $ret = self::generateFromPath($tmpPath, $dstPath, $width, $height); - unlink($tmpPath); - return $ret; - } - - return false; - - case 'image/jpeg': - $srcImage = imagecreatefromjpeg($srcPath); - break; - - case 'image/png': - $srcImage = imagecreatefrompng($srcPath); - break; - - case 'image/gif': - $srcImage = imagecreatefromgif($srcPath); - break; - - default: - throw new SimpleException('Invalid thumbnail file type'); - } - - $config = Core::getConfig(); - switch ($config->browsing->thumbStyle) - { - case 'outside': - $dstImage = ThumbnailHelper::cropOutside($srcImage, $width, $height); - break; - case 'inside': - $dstImage = ThumbnailHelper::cropInside($srcImage, $width, $height); - break; - default: - throw new SimpleException('Unknown thumbnail crop style'); - } - - imagejpeg($dstImage, $dstPath); - imagedestroy($srcImage); - imagedestroy($dstImage); - } -} diff --git a/src/Models/Entities/PostEntity.php b/src/Models/Entities/PostEntity.php index 7c6cce6b..3e011b61 100644 --- a/src/Models/Entities/PostEntity.php +++ b/src/Models/Entities/PostEntity.php @@ -393,12 +393,20 @@ final class PostEntity extends AbstractEntity implements IValidatable, ISerializ $height = Core::getConfig()->browsing->thumbHeight; $dstPath = $this->getThumbPath($width, $height); + $thumbnailGenerator = new SmartThumbnailGenerator(); + if (file_exists($this->getThumbCustomSourcePath())) - return ThumbnailHelper::generateFromPath($this->getThumbCustomSourcePath(), $dstPath, $width, $height); + { + return $thumbnailGenerator->generateFromFile( + $this->getThumbCustomSourcePath(), + $dstPath, + $width, + $height); + } if ($this->getType()->toInteger() == PostType::Youtube) { - return ThumbnailHelper::generateFromUrl( + return $thumbnailGenerator->generateFromUrl( 'http://img.youtube.com/vi/' . $this->getFileHash() . '/mqdefault.jpg', $dstPath, $width, @@ -406,7 +414,11 @@ final class PostEntity extends AbstractEntity implements IValidatable, ISerializ } else { - return ThumbnailHelper::generateFromPath($this->getFullPath(), $dstPath, $width, $height); + return $thumbnailGenerator->generateFromFile( + $this->getFullPath(), + $dstPath, + $width, + $height); } } diff --git a/src/ThumbnailGenerators/FlashThumbnailGenerator.php b/src/ThumbnailGenerators/FlashThumbnailGenerator.php new file mode 100644 index 00000000..ec79640e --- /dev/null +++ b/src/ThumbnailGenerators/FlashThumbnailGenerator.php @@ -0,0 +1,34 @@ +generateFromFile($tmpPath, $dstPath, $width, $height); + unlink($tmpPath); + return $ret; + } + + exec('swfrender ' . $srcPath . ' -o ' . $tmpPath); + + if (file_exists($tmpPath)) + { + $thumbnailGenerator = new ImageThumbnailGenerator(); + $ret = $thumbnailGenerator->generateFromFile($tmpPath, $dstPath, $width, $height); + unlink($tmpPath); + return $ret; + } + + return false; + } +} diff --git a/src/ThumbnailGenerators/IThumbnailGenerator.php b/src/ThumbnailGenerators/IThumbnailGenerator.php new file mode 100644 index 00000000..306404f8 --- /dev/null +++ b/src/ThumbnailGenerators/IThumbnailGenerator.php @@ -0,0 +1,5 @@ +browsing->thumbStyle) + { + case 'outside': + $dstImage = $this->cropOutside($srcImage, $width, $height); + break; + case 'inside': + $dstImage = $this->cropInside($srcImage, $width, $height); + break; + default: + throw new SimpleException('Unknown thumbnail crop style'); + } + + imagejpeg($dstImage, $dstPath); + imagedestroy($srcImage); + imagedestroy($dstImage); + + return true; + } + + private function cropOutside($srcImage, $dstWidth, $dstHeight) + { + $srcWidth = imagesx($srcImage); + $srcHeight = imagesy($srcImage); + + if (($dstHeight / $dstWidth) > ($srcHeight / $srcWidth)) + { + $h = $srcHeight; + $w = $h * $dstWidth / $dstHeight; + } + else + { + $w = $srcWidth; + $h = $w * $dstHeight / $dstWidth; + } + $x = ($srcWidth - $w) / 2; + $y = ($srcHeight - $h) / 2; + + $dstImage = imagecreatetruecolor($dstWidth, $dstHeight); + imagecopyresampled($dstImage, $srcImage, 0, 0, $x, $y, $dstWidth, $dstHeight, $w, $h); + return $dstImage; + } + + private function cropInside($srcImage, $dstWidth, $dstHeight) + { + $srcWidth = imagesx($srcImage); + $srcHeight = imagesy($srcImage); + + if (($dstHeight / $dstWidth) < ($srcHeight / $srcWidth)) + { + $h = $dstHeight; + $w = $h * $srcWidth / $srcHeight; + } + else + { + $w = $dstWidth; + $h = $w * $srcHeight / $srcWidth; + } + + $dstImage = imagecreatetruecolor($w, $h); + imagecopyresampled($dstImage, $srcImage, 0, 0, 0, 0, $w, $h, $srcWidth, $srcHeight); + return $dstImage; + } +} diff --git a/src/ThumbnailGenerators/ImageImagickThumbnailGenerator.php b/src/ThumbnailGenerators/ImageImagickThumbnailGenerator.php new file mode 100644 index 00000000..5f5892bf --- /dev/null +++ b/src/ThumbnailGenerators/ImageImagickThumbnailGenerator.php @@ -0,0 +1,69 @@ +coalesceImages(); + + $config = Core::getConfig(); + switch ($config->browsing->thumbStyle) + { + case 'outside': + $this->cropOutside($image, $width, $height); + break; + case 'inside': + $this->cropInside($image, $width, $height); + break; + default: + throw new SimpleException('Unknown thumbnail crop style'); + } + + $image->writeImage($dstPath); + $image->destroy(); + + return true; + } + + private function cropOutside($srcImage, $dstWidth, $dstHeight) + { + $srcWidth = $srcImage->getImageWidth(); + $srcHeight = $srcImage->getImageHeight(); + + if (($dstHeight / $dstWidth) > ($srcHeight / $srcWidth)) + { + $h = $dstHeight; + $w = $h * $srcWidth / $srcHeight; + } + else + { + $w = $dstWidth; + $h = $w * $srcHeight / $srcWidth; + } + $x = ($srcWidth - $w) / 2; + $y = ($srcHeight - $h) / 2; + + $srcImage->resizeImage($w, $h, imagick::FILTER_LANCZOS, 0.9); + $srcImage->cropImage($dstWidth, $dstHeight, ($w - $dstWidth) >> 1, ($h - $dstHeight) >> 1); + $srcImage->setImagePage(0, 0, 0, 0); + } + + private function cropInside($srcImage, $dstWidth, $dstHeight) + { + $srcWidth = $srcImage->getImageWidth(); + $srcHeight = $srcImage->getImageHeight(); + + if (($dstHeight / $dstWidth) < ($srcHeight / $srcWidth)) + { + $h = $dstHeight; + $w = $h * $srcWidth / $srcHeight; + } + else + { + $w = $dstWidth; + $h = $w * $srcHeight / $srcWidth; + } + + $srcImage->resizeImage($w, $h, imagick::FILTER_LANCZOS, 0.9); + } +} diff --git a/src/ThumbnailGenerators/ImageThumbnailGenerator.php b/src/ThumbnailGenerators/ImageThumbnailGenerator.php new file mode 100644 index 00000000..7344cf14 --- /dev/null +++ b/src/ThumbnailGenerators/ImageThumbnailGenerator.php @@ -0,0 +1,15 @@ +generateFromFile($srcPath, $dstPath, $width, $height); + } +} diff --git a/src/ThumbnailGenerators/SmartThumbnailGenerator.php b/src/ThumbnailGenerators/SmartThumbnailGenerator.php new file mode 100644 index 00000000..a884ecc5 --- /dev/null +++ b/src/ThumbnailGenerators/SmartThumbnailGenerator.php @@ -0,0 +1,48 @@ +generateFromFile($srcPath, $dstPath, $width, $height); + + case 'video/mp4': + case 'video/webm': + case 'video/ogg': + case 'application/ogg': + case 'video/x-flv': + case 'video/3gpp': + $thumbnailGenerator = new VideoThumbnailGenerator(); + return $thumbnailGenerator->generateFromFile($srcPath, $dstPath, $width, $height); + + case 'image/jpeg': + case 'image/png': + case 'image/gif': + $thumbnailGenerator = new ImageThumbnailGenerator(); + return $thumbnailGenerator->generateFromFile($srcPath, $dstPath, $width, $height); + + default: + throw new SimpleException('Invalid thumbnail file type'); + } + } +} diff --git a/src/ThumbnailGenerators/VideoThumbnailGenerator.php b/src/ThumbnailGenerators/VideoThumbnailGenerator.php new file mode 100644 index 00000000..389b2e21 --- /dev/null +++ b/src/ThumbnailGenerators/VideoThumbnailGenerator.php @@ -0,0 +1,38 @@ +generateFromFile($tmpPath, $dstPath, $width, $height); + unlink($tmpPath); + return $ret; + } + + $cmd = sprintf( + 'ffmpeg -i "%s" -vframes 1 "%s"', + $srcPath, + $tmpPath); + exec($cmd); + + if (file_exists($tmpPath)) + { + $thumbnailGenerator = new ImageThumbnailGenerator(); + $ret = $thumbnailGenerator->generateFromFile($tmpPath, $dstPath, $width, $height); + unlink($tmpPath); + return $ret; + } + + return false; + } +} diff --git a/src/core.php b/src/core.php index 85be5039..533c374b 100644 --- a/src/core.php +++ b/src/core.php @@ -70,7 +70,7 @@ final class Core TransferHelper::createDirectory($config->main->thumbsPath); //extension sanity checks - $requiredExtensions = ['pdo', 'pdo_' . $config->main->dbDriver, 'gd', 'openssl', 'fileinfo']; + $requiredExtensions = ['pdo', 'pdo_' . $config->main->dbDriver, 'openssl', 'fileinfo']; foreach ($requiredExtensions as $ext) if (!extension_loaded($ext)) die('PHP extension "' . $ext . '" must be enabled to continue.' . PHP_EOL);