232 lines
5.5 KiB
PHP
232 lines
5.5 KiB
PHP
<?php
|
|
/**
|
|
* Nextcloud - Gallery
|
|
*
|
|
* This file is licensed under the Affero General Public License version 3 or
|
|
* later. See the COPYING file.
|
|
*
|
|
* @author Olivier Paroz <galleryapps@oparoz.com>
|
|
*
|
|
* @copyright Olivier Paroz 2017
|
|
*/
|
|
|
|
namespace OCA\Gallery\Service;
|
|
|
|
use OCP\Files\File;
|
|
use OCP\Image;
|
|
use OCP\IPreview;
|
|
use OCP\ILogger;
|
|
|
|
use OCA\Gallery\Environment\Environment;
|
|
|
|
/**
|
|
* Generates previews
|
|
*
|
|
* @package OCA\Gallery\Service
|
|
*/
|
|
class PreviewService extends Service {
|
|
|
|
use Base64Encode;
|
|
|
|
/** @var IPreview */
|
|
private $previewManager;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param string $appName
|
|
* @param Environment $environment
|
|
* @param IPreview $previewManager
|
|
* @param ILogger $logger
|
|
*/
|
|
public function __construct(
|
|
$appName,
|
|
Environment $environment,
|
|
IPreview $previewManager,
|
|
ILogger $logger
|
|
) {
|
|
parent::__construct($appName, $environment, $logger);
|
|
|
|
$this->previewManager = $previewManager;
|
|
}
|
|
|
|
/**
|
|
* Decides if we should download the file instead of generating a preview
|
|
*
|
|
* @param File $file
|
|
* @param bool $animatedPreview
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isPreviewRequired($file, $animatedPreview) {
|
|
$mime = $file->getMimeType();
|
|
|
|
if ($mime === 'image/svg+xml') {
|
|
return $this->isSvgPreviewRequired();
|
|
}
|
|
if ($mime === 'image/gif') {
|
|
return $this->isGifPreviewRequired($file, $animatedPreview);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns an array containing everything needed by the client to be able to display a preview
|
|
*
|
|
* * fileid: the file's ID
|
|
* * mimetype: the file's media type
|
|
* * preview: the preview's content
|
|
*
|
|
* Example logger
|
|
* $this->logger->debug(
|
|
* "[PreviewService] Path : {path} / mime: {mimetype} / fileid: {fileid}",
|
|
* [
|
|
* 'path' => $preview['data']['path'],
|
|
* 'mimetype' => $preview['data']['mimetype'],
|
|
* 'fileid' => $preview['fileid']
|
|
* ]
|
|
* );
|
|
*
|
|
* @todo Get the max size from the settings
|
|
*
|
|
* @param File $file
|
|
* @param int $maxX asked width for the preview
|
|
* @param int $maxY asked height for the preview
|
|
* @param bool $keepAspect
|
|
* @param bool $base64Encode
|
|
*
|
|
* @return string|\OC_Image|string|false preview data
|
|
* @throws InternalServerErrorServiceException
|
|
*/
|
|
public function createPreview(
|
|
$file, $maxX = 0, $maxY = 0, $keepAspect = true, $base64Encode = false
|
|
) {
|
|
try {
|
|
$preview = $this->previewManager->getPreview($file, $maxX, $maxY, !$keepAspect);
|
|
$img = new Image($preview->getContent());
|
|
$mimeType = $img->mimeType();
|
|
if ($img && $base64Encode) {
|
|
$img = $this->encode($img);
|
|
}
|
|
|
|
return [
|
|
'preview' => $img,
|
|
'mimetype' => $mimeType
|
|
];
|
|
} catch (\Exception $exception) {
|
|
throw new InternalServerErrorServiceException('Preview generation has failed');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the passed mime type is supported
|
|
*
|
|
* In case of a failure, we just return that the media type is not supported
|
|
*
|
|
* @param string $mimeType
|
|
*
|
|
* @return boolean
|
|
*/
|
|
private function isMimeSupported($mimeType = '*') {
|
|
try {
|
|
return $this->previewManager->isMimeSupported($mimeType);
|
|
} catch (\Exception $exception) {
|
|
unset($exception);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decides if we should download the SVG or generate a preview
|
|
*
|
|
* SVGs are downloaded if the SVG converter is disabled
|
|
* Files of any media type are downloaded if requested by the client
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isSvgPreviewRequired() {
|
|
return $this->isMimeSupported('image/svg+xml');
|
|
}
|
|
|
|
/**
|
|
* Decides if we should download the GIF or generate a preview
|
|
*
|
|
* GIFs are downloaded if they're animated and we want to show
|
|
* animations
|
|
*
|
|
* @param File $file
|
|
* @param bool $animatedPreview
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isGifPreviewRequired($file, $animatedPreview) {
|
|
$gifSupport = $this->isMimeSupported('image/gif');
|
|
$animatedGif = $this->isGifAnimated($file);
|
|
|
|
return $gifSupport && !($animatedGif && $animatedPreview);
|
|
}
|
|
|
|
/**
|
|
* Tests if a GIF is animated
|
|
*
|
|
* An animated gif contains multiple "frames", with each frame having a
|
|
* header made up of:
|
|
* * a static 4-byte sequence (\x00\x21\xF9\x04)
|
|
* * 4 variable bytes
|
|
* * a static 2-byte sequence (\x00\x2C) (Photoshop uses \x00\x21)
|
|
*
|
|
* We read through the file until we reach the end of the file, or we've
|
|
* found at least 2 frame headers
|
|
*
|
|
* @link http://php.net/manual/en/function.imagecreatefromgif.php#104473
|
|
*
|
|
* @param File $file
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isGifAnimated($file) {
|
|
$count = 0;
|
|
$fileHandle = $this->isFileReadable($file);
|
|
if ($fileHandle) {
|
|
while (!feof($fileHandle) && $count < 2) {
|
|
$chunk = fread($fileHandle, 1024 * 100); //read 100kb at a time
|
|
$count += preg_match_all(
|
|
'#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches
|
|
);
|
|
}
|
|
fclose($fileHandle);
|
|
}
|
|
|
|
return $count > 1;
|
|
}
|
|
|
|
/**
|
|
* Determines if we can read the content of the file and returns a file pointer resource
|
|
*
|
|
* We can't use something like $node->isReadable() as it's too unreliable
|
|
* Some storage classes just check for the presence of the file
|
|
*
|
|
* @param File $file
|
|
*
|
|
* @return resource
|
|
* @throws InternalServerErrorServiceException
|
|
*/
|
|
private function isFileReadable($file) {
|
|
try {
|
|
$fileHandle = $file->fopen('rb');
|
|
if (!$fileHandle) {
|
|
throw new \Exception();
|
|
}
|
|
} catch (\Exception $exception) {
|
|
throw new InternalServerErrorServiceException(
|
|
'Something went wrong when trying to read' . $file->getPath()
|
|
);
|
|
}
|
|
|
|
return $fileHandle;
|
|
}
|
|
|
|
}
|