Refactored thumb generator; added imagick support

This commit is contained in:
Marcin Kurczewski 2014-05-19 19:21:58 +02:00
parent fc486190c2
commit dcfe6a00ea
10 changed files with 314 additions and 169 deletions

View file

@ -1,165 +0,0 @@
<?php
class ThumbnailHelper
{
public static 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;
}
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);
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,34 @@
<?php
class FlashThumbnailGenerator implements IThumbnailGenerator
{
public function generateFromFile($srcPath, $dstPath, $width, $height)
{
$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))
{
$thumbnailGenerator = new ImageThumbnailGenerator();
$ret = $thumbnailGenerator->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;
}
}

View file

@ -0,0 +1,5 @@
<?php
interface IThumbnailGenerator
{
public function generateFromFile($srcPath, $dstPath, $width, $height);
}

View file

@ -0,0 +1,89 @@
<?php
class ImageGdThumbnailGenerator implements IThumbnailGenerator
{
public function generateFromFile($srcPath, $dstPath, $width, $height)
{
$mime = mime_content_type($srcPath);
switch ($mime)
{
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 image type');
}
$config = Core::getConfig();
switch ($config->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;
}
}

View file

@ -0,0 +1,69 @@
<?php
class ImageImagickThumbnailGenerator implements IThumbnailGenerator
{
public function generateFromFile($srcPath, $dstPath, $width, $height)
{
$image = new Imagick($srcPath);
$image = $image->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);
}
}

View file

@ -0,0 +1,15 @@
<?php
class ImageThumbnailGenerator implements IThumbnailGenerator
{
public function generateFromFile($srcPath, $dstPath, $width, $height)
{
if (extension_loaded('imagick'))
$realImageGenerator = new ImageImagickThumbnailGenerator();
elseif (extension_loaded('gd'))
$realImageGenerator = new ImageGdThumbnailGenerator();
else
return false;
return $realImageGenerator->generateFromFile($srcPath, $dstPath, $width, $height);
}
}

View file

@ -0,0 +1,48 @@
<?php
class SmartThumbnailGenerator implements IThumbnailGenerator
{
public function generateFromUrl($url, $dstPath, $width, $height)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
TransferHelper::download(
$url,
$tmpPath,
null);
$ret = self::generateFromFile($tmpPath, $dstPath, $width, $height);
unlink($tmpPath);
return $ret;
}
public function generateFromFile($srcPath, $dstPath, $width, $height)
{
$mime = mime_content_type($srcPath);
switch ($mime)
{
case 'application/x-shockwave-flash':
$thumbnailGenerator = new FlashThumbnailGenerator();
return $thumbnailGenerator->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');
}
}
}

View file

@ -0,0 +1,38 @@
<?php
class VideoThumbnailGenerator implements IThumbnailGenerator
{
public function generateFromFile($srcPath, $dstPath, $width, $height)
{
$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))
{
$thumbnailGenerator = new ImageThumbnailGenerator();
$ret = $thumbnailGenerator->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;
}
}

View file

@ -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);